Routing is one of the key requirements in modern applications, especially in Zend Framework 2; but they shouldn’t be overly-complicated. Today, we’re going to look at how to build a routing table, simply and easily using child and segment routes.
Before I cover how to do it though, let’s first set the scene, laying out the reason why. That way, the examples will be more practical, holding more semantic meaning. Recently I’ve been building an application to scratch an itch of mine, helping me forecast my freelance writing income and workload.
When finished, the application will show me what’s in my writing pipeline (upcoming, submitted and published work), and through that, how much I can expect to earn and by when. To keep it simple, the app’s composed of two controllers: earnings and pipeline. And these controllers will have commonality between them, specifically with the routes. Here’s some examples of what I mean:
- This Week, This Month, This Quarter, This Year
- Next Week, Next Month, Next Quarter, Next Year
- Last Week, Last Month, Last Quarter, Last Year
You can see that whilst there are quite a number of routes, they have two common patterns. The first is a period specifier, the second is a period type. i.e.:
- Period Specifier: this, next and last
- Period Type: week, month, quarter and year
So despite having a lot of potential routes, they all boil down to two parameters which we can react to in the controllers, via a switch statement or a class implementing the factory pattern. Given that, they should be combined into only a few, composite, routes in our routing table. Wouldn’t you agree?
But how would we do that? Gladly, it’s quite simply, using a combination of two route types: ****Segment and Child Routes. I’ve made a complete example, which’s available in this Gist. Feel free to skip straight to that. But otherwise, let’s step through the annotated version together.
return array(
'router' => array(
'routes' => array(
'forecaster' => array(
'type' => 'Literal',
'options' => array(
'route' => '/forecaster',
'defaults' => array(
'__NAMESPACE__' => 'Forecaster\Controller',
'controller' => 'Forecaster',
'action' => 'index',
),
),
'may_terminate' => true,
In our module’s module.config.php, we’ve first setup a Literal route, which contains the route prefix:
/forecaster
, along with the default controller and action. All routes in this group will now start with /forecaster
, which makes sense.
'child_routes' => array(
Here, we setup the remaining routes, as child routes of the parent, which we just created.
'earnings-period' => array(
'type' => 'segment',
Here, we’ve created our new, composite, route, using the segment type. This allows us to have segments, or route parameters, which can be both interchanged, and accessed in controllers using the fromRoute
method, on $this->params()
, which we’ll see an example of shortly.
'options' => array(
'route' => '/earnings/[:period]/[:type]',
Here we’ve specified our route pattern or segment structure. To match, all routes need to start with /earnings/
. Following this, they need to contain two further sections, period and type. An example of a matching route is: /forecaster/earnings/this/week
. You start to see how it’s easy to make multiple routes from one definition.
'constraints' => array(
'period' => '(this|previous|last|next|current)',
'type' => '(week|month|quarter|year)'
),
Now, we add the real magic. Here we set the constraints for what’s allowed in both the period and type segment elements.
- period can be one of: this, previous, last, next, or current
- type can be one of: week, month, quarter, or year
NB: You may be wondering why have previous and last, and this and current? There is no necessity to have these. They’re more utility options, just to make things a bit simpler for the user.
'defaults' => array(
'controller' => 'earnings',
'action' => 'view'
)
)
),
),
),
),
),
Finally, we finish up by specifying the controller and action to use, in this case, the view action on the earnings controller. That’s it. To handle the pipeline, we could just copy it, changing the default controller to &‘pipeline’.
If the constraint syntax looks a little foreign to you, I suggest getting to know the basics of regular expressions (regexes). Not only do these come in very handy, but they save you a lot of time and pre, when even basically mastered.
Accessing the Segments in the Controller
As I mentioned earlier, we need some way of accessing the segment information in the controller, so we can differentiate between the route specified. Gladly, it’s a no brainer. So far, my view action’s really rudimentary, which you can see below:
public function ViewAction()
{
$period = $this->params()->fromRoute('period');
$type = $this->params()->fromRoute('type');
return new ViewModel(array(
'period' => $period,
'type' => $type,
));
}
Here, I’ve initialised both type and period, by calling fromRoutes()
on $this->params
, which retrieves the named route parameter, or segment, from the last executed route. What could be simpler?
Security Alert
I need to stress that I've not added any security checks here and off the top of my head am not aware of any. But don't take this pre as a stellar example of security.
Wrapping Up
There we have it; a simple way to keep the composition of a routing table extremely compact. If your table is only small, you may not see a need to take this approach. But with time, I’m sure it, like everything else, will grow.
Have you seen this approach before? Would you use it? Share your thoughts in the comments and let’s kick of an animated discussion.
image credit - nicolasnova
Join the discussion
comments powered by Disqus