Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Dynamic Navigation using Zend\Navigation

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on November 18, 2012

Zend Framework 2 provide Navigation component to generate menu. It can utilize by creating static config in autoload/*.global.php. Sometime, we need dynamic menu to be generated in our application. I will give you a simple example to do that.

For example, i want structure menu like this :

So, we should do step by step like the following :
1. Create a menu Table (I’m using postgresql, you can specify yourself for your database )

CREATE TABLE menu
(
  id bigserial NOT NULL,
  name character varying(255),
  label character varying(255),
  route character varying(255),
  CONSTRAINT pk_menu PRIMARY KEY (id )
)
WITH (
  OIDS=FALSE
);
ALTER TABLE menu
  OWNER TO postgres;

2. Insert the data like the following :

3. create a Model :

namespace ZendSkeletonModule\Model;

use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\AbstractTableGateway;
use Zend\Db\Sql\Select;
use Zend\Db\Adapter\AdapterAwareInterface;

class MenuTable extends AbstractTableGateway
    implements AdapterAwareInterface
{
    protected $table = 'menu';

    public function setDbAdapter(Adapter $adapter)
    {
        $this->adapter = $adapter;
        $this->resultSetPrototype = new HydratingResultSet();

        $this->initialize();
    }

    public function fetchAll()
    {
        $resultSet = $this->select(function (Select $select){
                $select->order(array('id asc'));
        });

        $resultSet = $resultSet->toArray();

        return $resultSet;
    }
}

4. Extends Zend\Navigation\Service\DefaultNavigationFactory and override getPages() function.

namespace ZendSkeletonModule\Navigation;

use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Navigation\Service\DefaultNavigationFactory;

class MyNavigation extends DefaultNavigationFactory
{
    protected function getPages(ServiceLocatorInterface $serviceLocator)
    {
        if (null === $this->pages) {
            //FETCH data from table menu :
            $fetchMenu = $serviceLocator->get('menu')->fetchAll();

            $configuration['navigation'][$this->getName()] = array();
            foreach($fetchMenu as $key=>$row)
            {
                $configuration['navigation'][$this->getName()][$row['name']] = array(
                    'label' => $row['label'],
                    'route' => $row['route'],
                );
            }
            
            if (!isset($configuration['navigation'])) {
                throw new Exception\InvalidArgumentException('Could not find navigation configuration key');
            }
            if (!isset($configuration['navigation'][$this->getName()])) {
                throw new Exception\InvalidArgumentException(sprintf(
                    'Failed to find a navigation container by the name "%s"',
                    $this->getName()
                ));
            }

            $application = $serviceLocator->get('Application');
            $routeMatch  = $application->getMvcEvent()->getRouteMatch();
            $router      = $application->getMvcEvent()->getRouter();
            $pages       = $this->getPagesFromConfig($configuration['navigation'][$this->getName()]);

            $this->pages = $this->injectComponents($pages, $routeMatch, $router);
        }
        return $this->pages;
    }
}

5. Create Your Navigation Factory

namespace ZendSkeletonModule\Navigation;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class MyNavigationFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $navigation =  new MyNavigation();
        return $navigation->createService($serviceLocator);
    }
}

6. Register MenuTable model, and your Navigation in ServiceManager :

class Module
{
    public function getServiceConfig()
    {
        return array(
            'initializers' => array(
                function ($instance, $sm) {
                    if ($instance instanceof \Zend\Db\Adapter\AdapterAwareInterface) {
                        $instance->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
                    }
                }
            ),
            'invokables' => array(
                 'menu' => 'ZendSkeletonModule\Model\MenuTable' 
            ),
            'factories' => array(
                'Navigation' => 'ZendSkeletonModule\Navigation\MyNavigationFactory'
            )
          );
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }
 
    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                     __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }
}

6. Configuration done, let’s call from layout:

<div class="nav-collapse">
<?php echo $this->navigation('Navigation')->menu()->setUlClass('nav'); ?>
</div>

Done !

About these ads

45 Responses

Subscribe to comments with RSS.

  1. Ernesto Quintero said, on November 19, 2012 at 10:58 pm

    Excelente, Very good

  2. Dragos said, on November 20, 2012 at 2:58 am

    Tank you very much, I appreciate.

  3. Fredrik Wahlqvist (@FredWahlqvist) said, on November 20, 2012 at 6:02 am

    Really good examples, would be great to get examples on GitHub as well

  4. samsonasik said, on November 20, 2012 at 9:09 am

    You can follow my github account : https://github.com/samsonasik

  5. Jurian Sluiman said, on November 23, 2012 at 6:13 pm

    You might take a look at ensemble as well (http://ensemble.github.com). The kernel (https://github.com/ensemble/EnsembleKernel) does exactly this, together with two more options:

    1. Navigation can be a tree and not only a flat structure
    2. Like the tree structure of the navigation component, routes can be hierarchical as well

    The kernel implements an adapter pattern where currently only a doctrine one exists, but it’s easy to make one for Zend\Db as well.

    • samsonasik said, on November 24, 2012 at 12:40 pm

      great!, my post is just a sample to who are need simple implementation of dynamic navigation.

  6. Dragos said, on November 27, 2012 at 11:47 pm

    I apologize if too bold but may I suggest another blog post: Multi step form

  7. [...] Zend Framework 2 : Dynamic Navigation using ZendNavigation Автор: Abdul Malik Ikhsan Перевод: Лобач [...]

  8. Ashish Saxena said, on December 22, 2012 at 3:28 pm

    it would have been great if you explained the code ..
    not in details but few lines below each block explaining what is was doing.
    Must have really helped the beginners. Anyways nice post indeed .. :)

  9. myth said, on January 3, 2013 at 5:03 pm

    HJ samsonasik!

    has error how to fix?
    Fatal error: Uncaught exception ‘Zend\Db\TableGateway\Exception\RuntimeException’ with message ‘This table does not have an Adapter setup’ in E:\webproject\ZendFramework2\library\Zend\Db\TableGateway\AbstractTableGateway.php:104 Stack trace: #0 E:\webproject\ZendFramework2\library\Zend\Db\TableGateway\AbstractTableGateway.php(187): Zend\Db\TableGateway\AbstractTableGateway->initialize() #1 E:\webproject\htdocs\zf2shopcms\backend\Admin\src\Admin\Model\MenuTable.php(27): Zend\Db\TableGateway\AbstractTableGateway->select(Object(Closure)) #2 E:\webproject\htdocs\zf2shopcms\backend\Admin\src\Admin\Navigation\MyNavigation.php(13): Admin\Model\MenuTable->fetchAll() #3 E:\webproject\ZendFramework2\library\Zend\Navigation\Service\AbstractNavigationFactory.php(40): Admin\Navigation\MyNavigation->getPages(Object(Zend\ServiceManager\ServiceManager)) #4 E:\webproject\htdocs\zf2shopcms\backend\Admin\src\Admin\Navigation\MyNavigationFactory.php(12): Zend\Navigation\Service\AbstractNavigationFactory->createService(Object(Zend\ServiceMana in E:\webproject\ZendFramework2\library\Zend\ServiceManager\ServiceManager.php on line 733

    • samsonasik said, on January 3, 2013 at 6:27 pm

      You have to set adapter first.

      • myth said, on January 5, 2013 at 1:50 pm

        thank you for support!

        i’m write getServiceConfig() in module.php file:

        public function getServiceConfig()
        {
        return array(
        ‘factories’ => array(
        ‘Admin\Model\Users’ => function($sm) {
        $dbAdapter = $sm->get(‘Zend\Db\Adapter\Adapter’);
        $table = new Users($dbAdapter);
        return $table;
        },
        ‘Admin\Model\GroupUsers’ => function($sm) {
        $dbAdapter = $sm->get(‘Zend\Db\Adapter\Adapter’);
        $table = new GroupUsers($dbAdapter);
        return $table;
        },
        ),
        // menu config
        ‘invokables’ => array(
        ‘menu’ => ‘Admin\Model\MenuTable’
        ),
        ‘factories’ => array(
        ‘Navigation’ => ‘Admin\Navigation\MyNavigationFactory’
        )

        );
        }

        code remaining write like you!
        i’m not understand code is not running
        sorry english me not good :D

      • samsonasik said, on January 5, 2013 at 2:05 pm

        You should set adapter first. please take a look zf2 manual : http://zf2.readthedocs.org/en/latest/user-guide/database-and-models.html

  10. joenilson said, on January 13, 2013 at 11:29 am

    Hi, has you work with zend framework 2 and postgres database with multiple schemas?, i’m using zf2 v2.0.6 but always add double quotes to the schema.table name, i llok in the zf2 issue tracker but i dont see nothing like this. i’m using pdo driver to connect.

    • samsonasik said, on January 13, 2013 at 11:57 am

      in setDbAdapter, re-define the $table with TableIdentifier :

          public function setDbAdapter(Adapter $adapter)
          {
              $this->table = new \Zend\Db\Sql\TableIdentifier('menu', 'yourschema');
              $this->adapter = $adapter;
              $this->resultSetPrototype = new HydratingResultSet();
              
              $this->initialize();
          }
      
  11. Pouyan Az said, on February 7, 2013 at 8:06 pm

    Hi, very good tutorial, but I have now problem adding a second navigation bar. Is it posible to do that when yes how, I have read this but dosn’t help.

    http://stackoverflow.com/questions/12972316/how-to-set-up-2-navigations-in-zf2

  12. Dhananjay said, on February 8, 2013 at 4:00 am

    hey man your all tutorials are wonderfull. I followed your blog but i’m getting on issue that the address of all links showing only to home(www.weblogz2.loc). what could be the possible reason. please help me to rectify this issue.

    • samsonasik said, on February 8, 2013 at 1:46 pm

      check your module.config.php on route section, try with Literal first instead of Segment.

      • Dhananjay said, on February 9, 2013 at 4:06 am

        I’m really very thankful to you for reply, you are great instructor. This is my module.config.php file address http://pastie.org/6098043 Please if possible have a look of it and suggest me where i have to change for proper link address. i’m really messed up with this thing. if you can, then please mail me the changed file.

      • samsonasik said, on February 10, 2013 at 6:00 pm

        with your config. your call-ing route in navigation should be ‘album’ . and it should be work.

  13. aydinhassan said, on February 26, 2013 at 6:15 am

    Hi, I’ve just attempted this tutorial and It seems the Navigation factory never gets called. I put some debug in the factory and nothing prints. I’m using the latest version of Zend Framework 2, is this still the correct way to do it? Code is as follows: https://gist.github.com/anonymous/5034200

    The var_dump in DefaultNavigationFactory never happens.

  14. Bharat said, on March 9, 2013 at 10:20 pm

    Hi Samsonasik,

    I’m new in ZF2 (2.1.3).
    I follow entire step provided by you. But, I’m getting following error:

    ( ! ) Fatal error: Uncaught exception ‘Zend\Navigation\Exception\InvalidArgumentException’ with message ‘Invalid argument: Unable to determine class to instantiate’ in D:\wamp\www\zend\2.x\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php on line 744
    ( ! ) Zend\Navigation\Exception\InvalidArgumentException: Invalid argument: Unable to determine class to instantiate in D:\wamp\www\zend\2.x\vendor\zendframework\zendframework\library\Zend\Navigation\Page\AbstractPage.php on line 225

    Thanks
    Bharat

    • samsonasik said, on March 10, 2013 at 7:25 pm

      add the following common functions to your module class :

          public function getConfig()
          {
              return include __DIR__ . '/config/module.config.php';
          }
      
          public function getAutoloaderConfig()
          {
              return array(
                  'Zend\Loader\StandardAutoloader' => array(
                      'namespaces' => array(
                          __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                      ),
                  ),
              );
          }
      

      and don’t forget to set your db adapter. read the docs http://zf2.readthedocs.org/en/latest/user-guide/database-and-models.html

  15. Michael said, on March 31, 2013 at 9:35 pm

    Hello, samsonasik!

    I follow entire step provided by you and I’m getting error:

    Fatal error: Uncaught exception ‘Zend\ServiceManager\Exception\ServiceNotCreatedException’ with message ‘While attempting to create navigation(alias: Navigation) an invalid factory was registered for this instance type.’ in W:\domains\lp.loc\vendor\ZF2\library\Zend\ServiceManager\ServiceManager.php:871

    Thanks!

  16. MarianoV said, on April 18, 2013 at 1:28 am

    Hi, very nice code!!! how can I get the current or active module from here? thank you!!!

    • MarianoV said, on April 18, 2013 at 2:19 am

      I did it like this: $routeMatch->getMatchedRouteName();
      I have other question, if I want to invocate a function like this:
      echo $this->navigation(‘MyNavigation’)->testFunction();
      from layout.phtml, how can I did it?
      Thanks again!

      • samsonasik said, on April 18, 2013 at 3:33 am

        i don’t think so, don’t use it even possible, use view_helpers!

      • samsonasik said, on April 18, 2013 at 3:38 am

        i don’t think so. use view_helpers !

  17. samsonasik said, on April 18, 2013 at 3:34 am

    i don’t think so, use view_helpers to do something like that !

  18. Mauricio Silva said, on April 20, 2013 at 9:35 pm

    Hi, great code… helps a lot.
    But, I am wondering on how to make it work on two separate menus (default and admin) in a fancy way (with submenu and icons).
    I have made it work on a static way.

    • samsonasik said, on April 21, 2013 at 7:47 pm

      you can add class/id or/and other attributes beside of label and route.

  19. james said, on April 25, 2013 at 3:45 pm

    hi sam.
    could you show me how to use BREADCRUMBS by navigation . thanks a lot !!

  20. Philip said, on May 16, 2013 at 2:51 pm

    Awesome! Great work mate, keep it up!

    1 more thing, if you get a chance or you have free time maybe you can add another feature. Like implementing the BjyAuthorize(ACL) in navigation. It would be a great help for all beginners like me. Again, Thank you for this helpful blog!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 105 other followers

%d bloggers like this: