Use RouteMatch in Zend Framework 2 For Easy Routing

Today using Zend Framework 2 RouteMatch, Router and Request objects, I show you an easy way to dynamically update the current route. It’s almost painlessly simple.


Synopsis

Today using Zend Framework 2 RouteMatch, Router and Request objects, I show you an easy way to dynamically update the current route. It’s almost painlessly simple.

Working with Routes

In a Zend Framework 2 application that I’ve been building lately, I came across an unexpected routing situation; one that I’d not previously encountered. It started off pretty simply, with a small, segment, route that allowed me to render a page with two parameters: record status and page number.

It was for a fairly typical page that uses the Zend Paginator to paginate a large record set object and a status parameter to filter down the records displayed.

Well, like most things in web application development, what starts out simply in the beginner often grows more complex over time. So too is my once simple route. From humble origins, it’s evolved to have the following parameters:

<th align="left">
  <strong>Description</strong>
</th>
<td style="text-align: left;" align="center">
  The record status
</td>
<td style="text-align: left;" align="center">
  The current page number
</td>
<td style="text-align: left;" align="center">
  The amount of results per page
</td>
<td style="text-align: left;" align="center">
  The column to sort records by
</td>
<td style="text-align: left;" align="center">
  The sort direction (asc/desc)
</td>
<td align="left">
  The letter to filter the records by (a - z)
</td>
Parameter Name
status
page
perPage
sortBy
sortDir
filterLetter

Here’s a route in questions, so you can see how it’s configured:

'category-list' => array(
    'type'    => 'segment',
    'options' => array(
        'route'    => '/admin/category/list[/status/:status]
                          [/page/:page][/perPage/:perPage][/sortBy/:sortBy]
                          [/sortDir/:sortDir][/filterLetter/:filterLetter]',
        'defaults' => array(
            '__NAMESPACE__' => 'MaltBlueAdmin\Controller',
            'controller'    => 'Category',
            'action'        => 'list',
            'status' => 'all',
            'page' => 1,
            'perPage' => 10
            'sortBy' => "category-name"
            'sortDir' => "asc"
            'filterLetter' => ""
        ),
    ),
),

You can see that, as mentioned, I’m using the segment route type and all of the parameters are optional and have meaningful defaults. “What’s the big problem”, you ask. It’s an easy route to construct, has meaningful defaults. Add it to your module’s module.config.php and get going.

Well, I found it not so easy. You see, the route forms the foundation of a toolbar I’ve been creating in a custom view helper. It aims to give the user a large amount of power to filter the records available, so they can get to them quickly - but without overwhelming them with choice.

The toolbar works as follows: it sits atop the list of paginated records, followed by the list of records, then finally the pagination control. By default, no filter options are set. As desired, the user can then add different filters, of the types above. Have a look in the screenshots below and you’ll see a bit of what it looks like.

sample-list-display

In a nutshell, here is the problem. We’ve set the route parameters and rendered the page. How can we change, add or remove a parameter and re-render the current page, simply and easily? Said differently, when the user clicks a filter, how can I redirect to the same page, enable that filter and re-render paginated results?

Custom Zend Framework 2 ViewHelper with Paginated List - Filter by starting letter

Custom Zend Framework 2 ViewHelper with Paginated List - Set Results Per Page

At first I tried a rather convoluted function that used the EventManager in ZF2. To say the least, it was long and convoluted. Then I came across a post from Sam Minds that lead me to the eventual solution I present here today.

It turned out to be so simple, that I can keep adding further route options and the code stay’s pretty small. And to do it, all I needed was 3 components from the new Zend Framework 2:

  1. Zend\Mvc\Router\Http\TreeRouteStack
  2. Zend\Http\Request
  3. Zend\Mvc\Router\Http\RouteMatch

The key component though, is RouteMatch. If you’re not familiar with it, it looks as follows:

namespace Zend\Mvc\Router;

class RouteMatch
{
    public function __construct(array $params);
    public function setMatchedRouteName($name);
    public function getMatchedRouteName();
    public function setParam($name, $value);
    public function getParams();
    public function getParam($name, $default = null);
}

What it does is allow you to set and extract parameters from a given Route object, in my case, the current one. Even better, I only needed to use 3 functions. These are:

  • getMatchedRouteName
  • getParam
  • setParam

I’ll skip from the story now and show you the code, so you can see what I did. In the code below, you can see a part of my getViewHelperConfig definition in the module’s Module.php file. Here, I’ve use the factories element to initialise my new ViewHelper, passing in the Router and Request objects retrieved from the ServiceManager.

This gives me access to the core of the information that I need about both the current route and request.

namespace MaltBlue;

use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
use MaltBlue\View\Helper\ListViewToolbar;

class Module
{
    public function getViewHelperConfig()
    {
        return array(
            'factories' => array(
                'listViewToolbar' => function($sm) {
                    $locator = $sm->getServiceLocator();
                    return new ListViewToolbar(
                      $locator->get('Router'), $locator->get('Request')
                    );
                },
            ),
        );
    }

}

In the constructor of the ViewHelper, I initialise two class variables with the Router and Request objects obtained.

namespace CoalescentCore\View\Helper;

use Zend\View\Helper\AbstractHelper;
use Zend\Mvc\Router\Http\TreeRouteStack as Router;
use Zend\Http\Request;
use Zend\Mvc\Router\Http\RouteMatch;

class ListViewToolbar extends AbstractHelper
{
    protected $router;
    protected $request;
    protected $viewTemplate;
    protected $allowedFilters;

    public function __construct(Router $router, Request $request)
    {
        $this->router = $router;
        $this->request = $request;
        $this->viewTemplate = "/toolbar/listview";
    }

Next I have a method _getFilterRoute. This is responsible for determining and setting the record status filter in the route. First I initialise a RouteMatch object, by calling match on the Router object and pass in the Request object.

If the statusFilter supplied is in an allowed list (currently a simply array) I set the status route parameter to the value supplied.

After that, I then assemble a new route, by calling the assemble function on the router object. I pass to assemble the complete, updated, list of route parameters (the previous list with the one change that I’ve just made) - along with the route name, retrieved with the getMatchedRouteName function. From this the function returns a fully formed Url.

protected function _getFilterRoute($statusFilter = 'all')
    {
        $routeMatch = $this->router->match($this->request);

        if (in_array($statusFilter, $this->allowedFilters)) {
            $routeMatch->setParam('status', $statusFilter);
        } else {
            $routeMatch->setParam('status', 'all');
        }

        return $this->router->assemble(
            $routeMatch->getParams(),
            array('name' => $routeMatch->getMatchedRouteName())
        );
    }

Finally, in the __invoke method, the returned Url is passed as the value of a view parameter, activeFilterLink, and the rendered view template is returned.

public function __invoke($searchPlaceholder, $newItemName, $newItemRoute)
    {
        return $this->getView()->render($this->viewTemplate, array(
            'activeFilterLink' => $this->_getFilterRoute(
               $statusFilter = 'active'
            ),
        ));
    }
NB: I don't have too much space today to cover custom ViewHelpers, sorry. But have a look at the great online documentation to find out more.

So now, using a limited amount of custom code combined with the right objects and methods available in Zend Framework 2 and I can dynamically adjust an existing route on the fly. One last thing - the code sample will be up on the Malt Blue Github repository soon. Just need a bit more time.

Over To You

What do you think? Have you encountered a situation that could do with the solution presented in today’s post? Is there a simpler way that I’m overlooking (or haven’t learned yet)? Tell me today - share your thoughts, ideas and opinion in the comments!

Need help learning Zend Framework 2?

If you need a hand learning this amazing framework - you’re in luck. I’m actively working on a new Zend Framework 2 book, called Zend Framework 2 - For Beginners. Sign up today and be notified as soon as it’s ready. Plus - get samples and spoilers while you wait.

image copyright idvsolutions


You might also be interested in these tutorials too...

Thu, Jun 20, 2013

\Zend\Db\Sql - Build SQL Where Clauses Easily and Efficiently

In this tutorial we’re working through the basics of \Zend\Db\Sql\Where, showing how to build SQL Where clauses for database queries. We’ll show how to create different predicates from simple to nested queries using of and, or, like, between and in conditions.

Wed, Jan 30, 2013

Zend ServiceManager - Web Application Development Simplified

The Zend ServiceManager simplifies the web application development process by making configuration a breeze. In this, the 4th post introducing Zend Framework 2, you will learn what the ServiceManager is how to use it and how it simplifies applicaiton development time.

Wed, Jan 2, 2013

Zend Framework 2 Modules - The Application's Heart

Zend Framework 2 Modules - The Application's Heart

If I have seen further it is by standing on the shoulders of giants.

It’s a really exciting time at the moment with Zend Framework 2 gaining so much traction, after being stable for some time now.

Though I and countless others really enjoyed the 1.x series, it did leave some things to be desired - to be fair.

But the more I explore of the 2.x series, the more I honestly can say that I’m very impressed with it. It may not be as fast as the previous series, but with respect to development, there’s so much going for it it’s worth shouting about.

So it really is rewarding and exciting to begin covering all that it has to offer us. In part one of this series, I looked at a central concept of the revised framework - Dependency Injection.

Wed, Aug 8, 2012

How to Use the Zend Form ViewScript Decorator In All Modules

If you’ve been using Zend Forms for any length of time, you’ll know just how flexible and configurable they are. There’s not much that you can’t do with them, But it’s not always easy and there are catches. Today we look at ensuring that module-based Zend Forms using the ViewScript decorator can always be initialised no matter what.


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus