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 authorThis 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 authorAs 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: UserAs 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!