Creating a routing-based menu in symfony 1.2, part 1: the structure

March 31st, 2009 | by David |

The new symfony routing system with object routes and other niceties is so useful that you might consider basing your application navigation upon it. In my following articles I want to show you how to get a fully configurable multi-level menu system based on a minimalistic yaml syntax (and you could replace the yaml definition with anything else you like, be it XML, a database or whatever you want!). Sounds interesting? Read on!

Structure and markup

Let’s suppose we want a 2-level navigation, something looking like this:

* Home
    * Home
    * About
* Articles
    * Article index
    * View article details
    * Edit article
    * Edit artice comments
    * View article author

This is a fictional layout assuming that “Article index” provides some list to select from (this could also be a search form). As long as there is no article selected, the last 4 items have to appear disabled. The currently selected menu item also needs the class set to “current”.

In case the article index is currently selected the HTML code for these 2 menus could look like this:

Main menu:
<ul>
    <li><a href="[route_to_home]">Home</a></li>
    <li class="current"><a href="[route_to_article_index]">Articles</a></li>
</ul>
 
Sub menu:
<ul>
    <li class="current"><a href="[route_to_article_index]">Article index</a></li>
    <li class="disabled"><a>View article details</a></li>
    <li class="disabled"><a>Edit article</a></li>
    <li class="disabled"><a>Edit article comments</a></li>
    <li class="disabled"><a>View article author</a></li>
</ul>

This HTML markup is compatible to the commonly used Sliding doors of CSS technique – I prefer class=”current” to id=”current”, but the rest is the same. The disabled class is of course an addition you have to define yourself.

Defining the structure through YAML

Let’s cast this into a yaml markup that can be used by symfony. I used app.yml because it is application specific in my case.

all:
  menu:
    items:
      homepage:
        title:   Home
        children:
          homepage:
            title:   Home
          about:
            title:   About
 
      article_index:
        title:   Articles
        children:
          article_index:
            title:   Article index
          article_view:
            title:   View article details
          article_edit:
            title:   Edit article
          article_comments_edit:
            title:   Edit article comments
          user_view:
            title:   View article author

As you can see each menu item is basically defined by 2 parameters: the route name (the key) and the title to display. A third parameter, children, holds nested menu items.

The routing

The routes are defined in routing.yml as usual:

homepage:
  url:   /
  param: { module: default, action: index }
 
about:
  url:   /about
  param: { module: default, action: about }
 
article_index:
  url:          /article_index
  class:        sfPropelRoute
  options:      { model: Article, type: list }
  param:        { module: article, action: index }
 
article_view:
  url:          /article/:id/view
  class:        sfPropelRoute
  options:      { model: Article, type: object }
  param:        { module: article, action: view }
  requirements: { sf_method: get }
 
article_edit:
  url:          /article/:id/edit
  class:        sfPropelRoute
  options:      { model: Article, type: object }
  param:        { module: article, action: edit }
  requirements: { sf_method: get }
 
article_update:
  url:          /article/:id/edit
  class:        sfPropelRoute
  options:      { model: Article, type: object }
  param:        { module: article, action: update }
  requirements: { sf_method: put }
 
article_comments_edit:
  url:          /article/:id/comments_edit
  class:        sfPropelRoute
  options:      { model: Article, type: object }
  param:        { module: article, action: editComments }
  requirements: { sf_method: get }
 
article_comments_update:
  url:          /article/:id/comments_edit
  class:        sfPropelRoute
  options:      { model: Article, type: object }
  param:        { module: article, action: updateComments }
  requirements: { sf_method: put }
 
user_view:
  url:          /user/:id/view
  class:        sfPropelRoute
  options:      { model: User, type: list }
  param:        { module: user, action: view }
  requirements: { sf_method: get }

I’m using the new sfPropelRoute objects whenever I can. Of course there are several alternatives to the approach I use here, but they will work as well as long as you are using sfObjectRoutes. Since the PHP code will use sfObjectRoutes, not sfPropelRoutes, it should also work with sfDoctrineRoutes – I haven’t tested this though.

If you don’t understand the yaml definitions so far, you should consult the symfony guide on routing or (even better) read the Jobeet tutorial day 5. Understanding how the routing is configured and accessed through symfony is essential for the oncoming parts of this article.

Finishing the structure

There are still a few more things we have to tell our menu definition: the required object classes for the sfPropelRoutes and the alias routes (the update routes in that case) that should lead to the same menu items:

all:
  menu:
    items:
      homepage:
        title:   Home
        children:
          homepage:
            title:   Home
          about:
            title:   About
 
      article_index:
        title:   Articles
        children:
          article_index:
            title:   Article index
          article_view:
            title:   View article details
            require_object_class: Article
          article_edit:
            title:   Edit article
            require_object_class: Article
            alias_routes: [article_update]
          article_comments_edit:
            title:   Edit article comments
            require_object_class: Article
            alias_routes: [article_comments_update]
          user_view:
            title:   View article author
            require_object_class: User

As you can see there are 2 additional options you can define: require_object_class and alias_routes.
require_object_class tells the menu that the route requires an object of a certain type (consider the url_for() syntax for sfObjectRoutes). alias_routes tells the menu that there are alternative routes that should lead to the same menu item (to mark it as “current”).

That’s it for today. In my next article (part 2) I will show you how to generate menu items recursively out of this yaml definition and how the menu items interact with symfony’s routing system. In part 3 of this small series I plan to show all the advanced options of my menu system.

Have fun!

Post a Comment