Handling routines
An important aspect (if not the most important one) is the routing within Zend Framework 2. In its most basic form routing tells the framework how the user should get from page A to page B, and what needs to be done to arrive at that destination. That is why we generally think this is the most important part to understand if you are just starting out.
How to do it…
To define a route we can simply go into one of the configuration files and add the router configuration to there.
Setting up routing
Let's look at our simple (Segment
) configuration as follows (file: /module/Application/config/module.config.php
):
return array( // Here we define our route configuration 'routes' => array( // We give this route the name 'website' 'website' => array( // The route type is of the class: // Zend\Mvc\Router\Http\Segment 'type' => 'segment', // Lets set the options for this route 'options' => array( /* The route that we want to match is /website where we can optionally add a controller name and an action name. For example: /website/index/index */ 'route' => '/website[/:controller[/:action]]', /* We don't want to accept everything, but this regex makes sure we only accept alpha- numeric characters and a dash and underscore. In our instance we want to check this for the action and the controller. */ 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 'action' => '[a-zA-Z][a-zA-Z0-9_-]*' ), /* We want to make sure that if the user only types /website in the URL bar it will actually go somewhere. We defined that here. */ 'defaults' => array( 'controller' => 'Website\Controller\Index', 'action' => 'index' ), ), ), ), ), );
With this basic configuration we can easily define routes in our application, and in this instance we have configured a route that responds to the /website
URL. When we would go to the /website
URL, we would be routed to the Website\Controller\Index::indexAction
by default. If we however use the route /website/another/route
, we would be routed to the Website\Controller\Another::routeAction
, as we have defined that the controller and action can be parsed behind that. If we omit the route path and put in /website/another
, we would be redirected to the Website\Controller\Another::indexAction
, as that is used by default by the framework.
The preceding example has only one really major drawback, which is, when we decide to use anonymous function in the configuration to create more dynamic routes, we would not be able to cache the route as closures are not serializeable by the cache.
However, there is another method of declaring the route, and that is in the code. The need to create the route functionality in the code could (obviously everyone has their own reasons and requirements) arise because we want to cache the configuration in a later stage (as we cannot cache anonymous function, for example) or when we want to load up a route dynamically from a database.
Let's take a look at the /module/Application/Module.php
example:
<?php // We are working in the Application module namespace Application; // Our main imports that we want to use use Zend\Mvc\ModuleRouteListener; use Zend\Mvc\MvcEvent; // Define our module class, this is always 'Module', but // needs to be specifically created by the developer. class Module { public function onBootstrap(MvcEvent $e) { // First we want to get the ServiceManager $sm = $e->getApplication()->getServiceManager(); /* Say our logged in user is 'gdog' and we want him to be able to go to /gdog to see his profile. */ $user = 'gdog'; // Now get the router $router = $sm->get('router'); // Lets add a route called 'member' to our router $router->addRoute('member', array( /* We want to make /$user the main end point, with an optional controller and action. */ 'route' => '/'. $user. '[/:controller[/:action]]', /* We want a default end point (if no controller and action is given) to go to the index action of the index controller. */ 'defaults' => array( 'controller' => 'Member\Controller\Index', 'action' => 'index' ), /* We only want to allow alphanumeric characters with an exception to the dash and underscore. */ 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 'action' => '[a-zA-Z][a-zA-Z0-9_-]*' ), )); } }
Naturally there are more ways of adding a route, but the method mentioned in the preceding code for adding a route displays a canny way of dynamically adding a route. What we created there is that whenever Gdog goes to his profile, he can simply type in http://example.ext/gdog
and end up on his profile.
Even more wonderful is that if our friend Gdog wants to see his friends, he is able to do that by just typing in for example, http://example.ext/gdog/my/friends
, which will resolve to the Member
module and then go to the My
controller, lastly executing the Friends
action.
Using SimpleRouteStack
This route stack is—as the name implies—the simplest router around and is basically a list with routes that is being parsed to see which route matches, by default this type of router is not used in Zend Framework 2. The general rule of thumb is that if we want to add a route with a high priority, we give it a high index number for example, 100, or 200. If we want to give the route a very low priority, we would give it an index number of, for example, 5 or 10.
Giving priorities to routes comes in handy when we have very specific routes (which usually have a high priority) and less specific routes (low priority). If we, for example, want to make /website/url
redirect to a completely different module, controller, and action, but not affect the other website routes, we need to give the /website/url
route a higher priority so that when it is found, it will not search for the lower priority routes.
If we, by accident, turn the priorities around, we would find our /website/url
always redirect to the route that contains all the /website
routes.
SimpleRouteStack
uses a Zend\Mvc\Router\PriorityList
class to manage its routes priorities.
We need to consider routing before we want to start creating our application, as when the application grows we might get into trouble with our routing if we haven't considered 'how to route' beforehand. It would therefore be wise for us to 'sitemap' the application before coding the routes to make sure we have a correct route list and are not creating any conflicting routes.
The SimpleRouteStack
class has a number of methods defined that are very useful for us:
getRoute($name)
/getRoutes($name)
: This will retrieve the current route—if a name is provided—or routes that are defined in ourSimpleRouteStack
. If we are unsure about the routes we have defined, this would be a good place to check first.
addRoute($name, $route, $priority)
/ addRoutes($routes)
: We can use this to add a new route or an array of routes to our route type by simply adding it through this method. A route requires a name
, route
(which can be a string or an instance of RouteInterface
) and if we fancy a priority, we can give that as the third parameter.
hasRoute($name)
: If we would want to check whether a specific route already exists, we can search using its name
and find out if it does or doesn't.
removeRoute($name)
: When we are tired of a route we can simply give its name and remove it from the list. This can be particularly handy if we want for example to have a module override a certain /login when the user has logged in to route to/user.SimpleRouteStack
: Does not have a functionality to have multiple routes with the same priority. If there is a route with a priority already defined, it will prioritize the last route added as the route with the highest priority.
Using TreeRouteStack
Routers are not restricted to using the URI path to find out how to route a request. They can also use other information such as the query parameters, headers, methods, or hostnames to find a match.
How it works…
In Zend Framework 2, we will generally use routing that is based on a request URI, which contains path segments that should be queried. Routes are matched by a router, which utilizes RouteStack
to find the match to the query made by the router. We use RouteStack
because we want a decent way of managing our different routes. With Zend Framework 2 there are loads of route types provided, but only two flavorless routers namely SimpleRouteStack
and TreeRouteStack
.
When we are defining a router, we need to make sure we understand how it works. Although creating lists with different paths is simple enough, it is wise to remember that the Zend Framework 2 router generally works with the Last In First Out (LIFO) concept, which means that a route that would be used often would be registered last, and a route that is less common would be registered earlier in the router stack.
There's more…
Besides the two standard route types, Zend Framework 2 comes with a whole scale of route types that are more specialized to the Internet navigation or even through the console.
Namespace – Zend\Mvc\Router\Http
A wonderful set of HTTP routers can be found in the Zend\Mvc\Router\Http
namespace and we will take a quick look at the different classes that reside within this namespace.
The Hostname class explained
The Zend\Mvc\Router\Http\Hostname
namespace will try to match its routing against the hostname defined in the configuration. For example, if we define the route to be something.example.ext
, our router will make its routing decision based on the full URL. But, if we add a single colon at the beginning of that same route, for example: :something.example.ext
, the router would base its route on the something
variable, which could be anything from aardvark.example.ext
to zyxt.example.ext
.
The Literal class explained
The Zend\Mvc\Router\Http\Literal
class will literally match the path we give in. For example, if we put a route in there, which is /grouphug
, the route will only resolve to that URL, and nothing else.
Methods explained
The Zend\Mvc\Router\Http\Method
class is used when we want to match against an HTTP method instead of a segment or path. This could be, for example, a POST
, DELETE
and so on. The method is also called verb
by Zend Framework 2, which means that instead of a route
parameter, it requests a verb
parameter when adding the route, which is an excellent way to create RESTful APIs.
The Part class explained
The Zend\Mvc\Router\Http\Part
class is used to describe child_routes
in our routing configuration. This means that—although never used directly—we can define that /user/profile
is being redirected to use the UserController
, with the profile
action.
Let's consider the following configuration:
return array( // We begin our router configuration 'router' => array( // Define our routes 'routes' => array( // We are defining a route named 'Example' 'Example' => array( 'type' => 'Literal', 'options' => array( /* This route will resolve to /recipe which will resolve to the Example module's IndexController and execute the IndexAction. */ 'route' => 'recipe', 'defaults' => array( '__NAMESPACE__' => 'Example\Controller', 'controller' => 'Index', ), ), 'may_terminate' => true, /* Here we begin to define our Part route, which always begins with the 'child_routes' configuration. */ 'child_routes' => array( 'client' => array( 'type' => 'Literal', 'options' => array( /* This child route (or Part) will resolve to /recipe/foo and will call the fooAction in the IndexController. */ 'route' => '/foo', 'defaults' => array( 'action' => 'fooAction' ), ), ), ), ), ), ), );
Regex explained
The Zend\Mvc\Router\Http\Regex
class would be used when we have a complex routing structure that requires us to dynamically create the route. This would, for example, come in handy when we look at News sites, where posts are built up like /archive/some-subject-2013.html
. This fairly complex route (as some-subject-2013.html
is dynamic in our case) would require a Regex
router that can resolve the Controller, Action, and in our case also the output format.
Let's consider the following example:
// We begin our router configuration 'router' => array( // Define our routes 'routes' => array( // We are defining a route named 'Archive' 'Archive' => array( 'type' => 'Literal', 'options' => array( /* This route will resolve to /archive which will resolve to the Archive module's IndexController and execute the IndexAction. */ 'regex' => '/archive/(?<id>[a-zA-Z0-9_- ]+)(\.(?<format>(html|xml)))?', 'defaults' => array( '__NAMESPACE__' => 'Archive\Controller', 'controller' => 'Index', 'action' => 'indexAction', 'format' => 'html', ), 'spec' => '/archive/%id%.%format%', ), ), ), ),
In the preceding example, it is important to note that /archive/%id%.%format%
tells us that we will receive two parameters in our method called indexAction
that is, id
and format
.
The Scheme class explained
The Zend\Mvc\Router\Http\Scheme
class is always using the defaults
parameter and will accept only one other parameter, which is called scheme
and can only contain one of the following options, that is, http
, https
, and mailto
.
The Segment class explained
The Zend\Mvc\Router\Http\Segment
class is probably one of the most-used routers that we would use, as you can dynamically define the route and controller for any module by using, for example, /:controller/:action
, which is easily recognizable by the colon separation. We can define any constraints
to the segment by configuring only the use of alphanumeric characters or another definition that we would like to use.
An example of Segment
is given in the first example in the How to do it... section.