This past week I had the need to integrate Zend_Acl with a navigation and menu system. Unfortunately, the documentation took a couple reads for me to understand how Zend_Navigation worked; given this, I wanted to write a primer that others could use to get started with Zend_Navigation. This is a short guide to how Zend_Navigation works, and how you can use it to improve your application.
Some Critical Vocabulary
There is some critical vocabulary that needs to be understood about Zend_Navigation and its use. For starters, Zend_Navigation makes a distinction between pages and containers. A page is a page in your application, either internal (Mvc) or external (Uri). A container is anything that stores pages. The confusing part of this is that pages are containers in themselves. This allows you to build trees of pages.
If that was a bit confusing, I understand. For the purposes of this introduction, I’ll be calling them parents and children. When I use the word container I’ll be referring only to Zend_Navigation objects (concrete implementations of Zend_Navigation). When I use the word pages I’ll be referring to concrete implementations of Zend_Navigation_Page_Mvc or Zend_Navigation_Page_Uri.
Zend_Navigation also makes an important distinction between MVC pages and URI pages. There is an important difference between the two. MVC pages are usually meant to refer to the Zend Framework MVC structure; URI pages are designed to refer to complete URLs, internally or externally. Besides their intended uses, the API is fairly similar between the two objects.
Creating pages is easy enough to do. The following source code technically gives you a complete page:
$page = new Zend_Navigation_Page_Mvc();
Obviously this page would be pretty useless, so let’s discuss some of the things you can give a page.
$page = new Zend_Navigation_Page_Mvc(array('controller' => 'home', label = 'Home'));
Now that’s more like it! We’ve given the page two of several arguments that it can take (you can read all about them in the manual). In this case, we’ve told Zend_Navigation that we have a page whose controller is “home” and that should be labeled “Home”. If we were to turn this into a URL (or run $page->getHref()) we’d get “http://localhost/home” as a URL.
This might seem overly simple, but that’s all there is to creating pages. One of the beautiful things about pages is that they take all of their arguments as an array, which means they’re easy to serialize and store for later retrieval (something that’s highly recommended). Additionally, you can use a Zend_Config object to create pages, or even to create a whole tree of pages (more on that, next).
Single pages aren’t very useful to us. Typically navigation is arranged in a hierarchy of some kind, with some pages being parents of other pages. Zend Framework makes it easy for us to create trees that have parents with children.
I typically start with just a generic container class of Zend_Navigation:
$container = new Zend_Navigation();
Two things are of critical importance to note at this point: when I create my pages, pages themselves can be parents or children, or both. All the pages will go inside the $container object; child pages can go inside parent pages, and so on. So assume for the moment this is our hierarchy:
- Home - About | - Careers - Mission - Tools | - Free Tools - New Licenses - Products
Creating these pages is easy. First, we need an array of the pages (which we can store in a separate file or you can even create XML for it and use Zend_Config to grab the XML). Here is my array:
$navArray = array( array( 'controller' => 'index', 'label' => 'Home', ), array( 'controller' => 'about', 'label' => 'About', 'pages' => array( array( 'controller' => 'about', 'action' => 'careers', 'label' => 'Careers', ), array( 'controller' => 'about', 'action' => 'mission', 'label' => 'Mission', ), ), ), array( 'controller' => 'tools', 'label' => 'Tools', 'pages' => array( array( 'controller' => 'tools', 'action' => 'free', 'label' => 'Free Tools', ), array( 'controller' => 'tools', 'action' => 'licenses', 'label' => 'New Licenses', ), array( 'controller' => 'tools', 'action' => 'products', 'label' => 'Products', ), ), ), );
Next, once we have that array in our source code (through whatever means) we can make use of it:
$config = new Zend_Config($navArray); $nav = new Zend_Navigation(); $nav->addPages($config);
And that’s it! The tree will automatically build itself, and the pages will be in the right order, under the right parents, and inside the Navigation container. You can choose to do this in your Bootstrap or somewhere else (it’s up to you). It is recommended that you store this somewhere in some caching mechanism, to help ensure that you don’t do it on every pageload, but this is the general idea.
Finding Pages In Trees
Of course, now that we have all of those pages in a huge tree, how the heck do we ever find anything? The navigation object has built-in methods for finding our page objects, and can search by any number of categories.
One of the options we have for creating pages is to tag them, which allows us to find all pages by a certain tag. Obviously we didn’t do that, but never fear, we can search by label instead. Let’s say we need to get information about the Careers page:
$page = $nav->findOneBy('label', 'Careers');
And just like that, we have a page object we can use! Be aware that if Zend_Navigation doesn’t find a page, it returns null (rather than throwing an exception) so you should include some logic to ensure that an object is actually returned to you. Other search options are findBy() and findAllBy().
Zend_Navigation has an easy way to create trees of pages without too much hassle. What we haven’t covered is how to make these trees work for you, or how to include Zend_Acl along with them. Those topics will be covered in the next two entries on Zend_Navigation.