Use RouteMatch in Zend Framework 2 For Easy Routing

Easy routing with RouteMatch in Zend Framework 2

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:

Parameter Name Description
status The record status
page The current page number
perPage The amount of results per page
sortBy The column to sort records by
sortDir The sort direction (asc/desc)
filterLetter The letter to filter the records by (a – z)

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

Intermediate Tutorial
  • Deepak12

    You may want to visit this site too! http://www.comvigo.com

  • nomaanp

    Hi,
    Thanks for the wonderful post!!! Can you provide the above module please.

    • http://www.matthewsetter.com/ maltblue

      @nomaanp I’ll get it up on Github during the week. Thanks for the prompt.

  • amats

    Dear Matthew,
     
    I find your blog super and this entry helps me a lot further, but I cannot directly derive a solution from it to my problem:
     
    I would like to define routes of a page on the site in the CMS. The urls would look like this:
     
    or
    or

     
    Menu is like the main menu on the page. I can have submenu. And the visitor can choose a page from the submenu to be displayed.
     
    The number of submenus are (theoretically) not limited. I would like to pass the whole url after the domain to a Controller, which would look up a database table for the page to display.
     
    Also sometimes a url like this would also be used: . So this is the normal Segment type.
     
    In this case the Zend Router should find the best match. So if I have defined a specific controller that should be invoked, not the general one, that gets the whole URL as such.
     
    Which routing concepts should I use and how in this case?
     
    Thank you very much for your kind help!

  • Pingback: Routing und der ganze REST - Zend Framework Magazin