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:
Parameter Name
|
<th align="left">
<strong>Description</strong>
</th>
status
|
<td style="text-align: left;" align="center">
The record status
</td>
page
|
<td style="text-align: left;" align="center">
The current page number
</td>
perPage
|
<td style="text-align: left;" align="center">
The amount of results per page
</td>
sortBy
|
<td style="text-align: left;" align="center">
The column to sort records by
</td>
sortDir
|
<td style="text-align: left;" align="center">
The sort direction (asc/desc)
</td>
filterLetter
|
<td align="left">
The letter to filter the records by (a - z)
</td>
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.
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?
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:
- Zend\Mvc\Router\Http\TreeRouteStack
- Zend\Http\Request
- 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'
),
));
}
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
Join the discussion
comments powered by Disqus