Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice to a dynamic menu module based on a modular CMS

Alright, I know the title might be a bit strange. But I don't know how else to put it. Basically it comes down to something simple, but sometimes being a single developer makes you question things and make you want to discuss it with fellow developers.

A quick intro to the problem: I'm about to re-do my CMS based on a popular PHP Framework (Fyi: Laravel is my choice). The CMS will be modular (Blog, Users, Pages, Forms, etc.), but I also want the website navigation to be manageable by the website owners. This is where I started thinking about the best practice to do so.

So far, what I was thinking about is to create an interface "MenuBuilder" that defines the methods that need to be known to generate menu links for a given module. So, let's say my BlogModule has a class BlogMenuBuilder.

The MenuBuilder classes would have a method to build a link based on none or some parameters (like a record ID), a method to generate all links, a method to generate an array of options for a dropdown and so on.

Then the MenuModule would save the classname and optional paramaters to a table, together with the menu location (top, footer, sidebar, etc) and I could call MenuModule::build('top'), this function would find all links, sorted ofcourse, and build the links by calling BlogMenuBuilder::link('type', $optional_parameters);. Type would in this case be a named route from the Laravel framework. Eventually ,the MenuModule::build() method would cache instead of having to build it again every time.

Am I anywhere near a best practice? I feel like I'm heading in the right direction but since I have no other developers to think this thing thru with I was hoping to get some useable feedback from SO.

Thanks for your time!

like image 889
Joshua - Pendo Avatar asked Aug 03 '16 19:08

Joshua - Pendo


2 Answers

I think you're pretty much there as far a structure. Based on what I understand from your question (and being that you're using Laravel) I'd approach it like this:


Database

Name:    menus
Columns: id | builder | created_at          | updated_at          | deleted_at
         ---------------------------------------------------------------------
     eg. 1  | NULL    | 1985-10-26 01:20:00 | 1985-10-26 01:21:00 | NULL
         2  | blog    | 1985-10-26 01:20:00 | 1985-10-26 01:21:00 | NULL


Name:    links
Columns: id | menu_id | type  | resource_type | resource_id | url                       | created_at          | updated_at          | deleted_at
         -------------------------------------------------------------------------------------------------------------------------------
     eg. 1  | 1       | url   | NULL          | NULL        | http://example.com/blog/1 | 1985-10-26 01:20:00 | 1985-10-26 01:21:00 | NULL
         1  | 2       | route | blog          | 1           | NULL                      | 1985-10-26 01:20:00 | 1985-10-26 01:21:00 | NULL


Name:    meta (or options)
Columns: id | resource_type | resource_id | name     | value
         ---------------------------------------------------
     eg. 1  | menu          | 1           | bg_color | 000000
         2  | link          | 1           | target   | _blank

With this table setup you can store menus, links (either a route defined in the relevant builder or a url) and meta/option data for either menus or links.

Note: If you wanted to re-use links in multiple menus you could remove the menu_id from the links table add a menu_links table for a ManyToMany relationship between menus and links


Models

Simple enough, you'd want a model for each table ie. Menu, Link and Meta


Builders

Within the app directory of your project I'd place a Builders folder and potentially a MenuBuilders folder inside that. Using that structure you'd namespace each Builder class under App\Builders\MenuBuilders.

With each Builder class you could then either extend a main MenuBuilder class or, depending how much you'll change for each Builder, you could implement an interface. Here's a quick example using an interface (if you're not familier with interfaces I'd suggest a look at Laracasts PHP Bootcamp):

MenuBuilderInterface.php

namespace App\Builders\MenuBuilders;

interface MenuBuilderInterface{
    /* 
     * Build function
     *
     * @param object $menu
     */
    public build($menu);
}

BlogMenuBuilder.php

namespace App\Builders\MenuBuilders;

class BlogMenuBuilder implements MenuBuilderInterface {
    /* 
     * Build function
     *
     * @param object $menu
     */
    public build($menu)
    {
        // Here you can now build your menu using
        // the menu instance passed through
    }
}

If you didn't want to have to create all the methods contracted in the interface then maybe extending a main MenuBuilder class works better for you, it really depends on how different you see the menus being and whether you can re-use a lot of methods in a main MenuBuilder.


In Practise

So how would you use the above setup? Like you stated in your question, each menu defines it's builder in the database so we could create a simple build function in our Menu model to call the correct Builder class:

Menu.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Menu extends Model
{
    /**
     * Get the user's first name.
     *
     * @param  string  $value
     * @return string
     */
    public function build()
    {
        // First we define our namespace and get the builder based on the menu data
        $namespace = 'App\\Builders\\MenuBuilders\\';
        $builder   = !empty($this->builder)? $namespace.ucfirst($this->builder).'MenuBuilder' : $namespace.'MenuBuilder';

        // Then check the builder class defined definitely exists
        // If no, replace with main menu builder class
        $builder = class_exists($builder)? $builder : $namespace.'MenuBuilder';

        // Lastly we call the build method on a new instance of our builder
        // passing in our menu instance
        return new $builder()->build($this);
    }
}

Now all we need do in our view is call the build function on our menu instance:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Example Site</title>
</head>
<body>
    <div id="menu">
        
        <?php
        // Get our blog menu
        // - you would obviously more likely do this in your controller
        $menu = Menu::find(2);
        ?>
        {!! $menu->build() !!}

    </div>
</body>
</html>

There are always a few different ways you can structure this kind of thing. Inevitably you will figure out what's best for you based on the specific requirements of each outcome you're after, but I hope that helps!

like image 126
Steve O Avatar answered Nov 14 '22 21:11

Steve O


Well as everyone said, do what other big cms'es have done.

So on the UI: - have a select box of existing menus - have a button to create a new menu

  • when creating or adding a menu
  • you will have a button to add a new item
  • when new item is clicked you a box with all the pages -->

this part could be complex as how your site is: - contain static pages: / , /about , /contact - contain dynamic pages (just like wordpress) - contain dynamic posts (just like wordpress) - contain dynamic categories, tags whatever (just like wordpress) - free input box

like image 36
Tzook Bar Noy Avatar answered Nov 14 '22 20:11

Tzook Bar Noy