Route Cache in Zend Framework 2

There are several ways to improve the performance of a Zend Framework 2 (ZF2) application and in the “Learn ZF2″ I explained some of them. But not all of them. Below you will find an information how to speed up the ZF2 application routing.

In a typical ZF2 application evaluating the routes and finding a match can be time-consuming task. The more non-literal routes your application has the slower the route matching process will be. Below I will present you one possible way to speed up that process.

In ZF2 routing means finding a controller, action and set of parameters that correspond to the current URL and given request parameters. In ZF2 the routing process starts when the MVC ROUTE event is triggered. There is a listener with priority 1(one) attached to this event that takes care to find a route match. What we can do is attach a new listener to the MVC ROUTE event but with higher priority. This way our listener will be executed before the actual matching process is started. In it we can check against a cache system if for the provided path of the URL there is already a cached data that contains the match parameters. In addition we can add a second listener to the MVC ROUTE event with lower priority. It will be executed after the matching is done and we can use it to save the route match data to our caching system. A good initial place to put those listeners will be in the Application Module.php ( module/Application/Module.php )

The initial version of our two listeners looks like this:

<?php
namespace Application;
 
use Zend\Mvc\MvcEvent;
 
class Module
{   public function onBootstrap(MvcEvent $event) 
    {
        $eventManager        = $event->getApplication()->getEventManager();
 
        //..
        $eventManager->attach(MvcEvent::Route, array($this, 'routeLoad'), 999);
        $eventManager->attach(MvcEvent::Route, array($this, 'routeSave'), 0);
    }
 
    /**
     * Method that tries to load cached routes and speed up the matching
     * @param MvcEvent $event
     */
    public function routeLoad(MvcEvent $event)
    {
        // @todo: check if we have data in our cache
    }
 
    /**
     * Method that tries to save a route match into a cache system
     * @param MvcEvent $event
     */
    public function routeSave(MvcEvent $event)
    {
        $match = $event->getRouteMatch();
        if(!$match) {
            return;
        }
 
        // @todo: save the route match into the cache.
    }

We should take care to cache the route match for URLs that produce the same result. A good example of a request for which the routing can be cached is the case when we have GET request and no query params. For such events we can set a flag route-cacheable. Additionally we should set a flag in the event that specifies if a cached route match was used or it was calculated during this request. That will help us avoid caching already cached routes. So the code will start to look like this

// ...
 
    /**
     * Method that tries to load cached routes and speed up the matching
     * @param MvcEvent $event
     */
    public function routeLoad(MvcEvent $event)
    {
        $request = $event->getRequest(); 
        if(!(
            $request->getMethod() == \Zend\Http\Request::METHOD_GET && 
            $request->getQuery()->count() == 0
            )) {
            // We do not cache route match for requests that can produce
            // different match.    
            return ;
        }
 
        $event->setParam('route-cacheable', true);
 
        // @todo: check if we have data in our cache
        $inCache = false; // @todo: get the actual value
 
        if($inCache) {
            $event->setParam('route-cached', true);
        }
    }
 
    /**
     * Method that tries to save a route match into a cache system
     * @param MvcEvent $event
     */
    public function routeSave(MvcEvent $event)
    {
        $match = $event->getRouteMatch();
        if(!$match) {
            return;
        }
 
        if($event->getParam('route-cached') || !$event->getParam('route-cacheable')) {
            return;
        }
 
        // @todo: save the route match into the cache.
    }

The real trick to speed up the route matching is not to bypass it but to put on the top a route definition that is very fast to match. This definition should use the Literal route type and should contain the route name and route parameters stored in the cache from the actual matching. Creating such a route can be done with:

$cachedRoute = \Zend\Mvc\Router\Http\Literal::factory($cachedData->getOptions());

The router contains multiple routes ordered in a stack. And this stack is evaluated from top to bottom. On the top are the routes with higher priority and on the bottom are the ones with lower priority. We need to put this cached router on the top of the stack of routes that we have. This can be done with a code like:

$router = $event->getRouter();
$router->addRoute($cachedData->getRouteName(), $cachedRoute, 99999);

In the routeLoad method the new code will look like this:

/**
     * Method that tries to load cached routes and speed up the matching
     * @param MvcEvent $event
     */
    public function routeLoad(MvcEvent $event)
    {
        $request = $event->getRequest(); 
        if(!(
            $request->getMethod() == \Zend\Http\Request::METHOD_GET && 
            $request->getQuery()->count() == 0
            )) {
            // We do not cache route match for requests that can produce
            // different match.    
            return ;
        }
 
        $event->setParam('route-cacheable', true);
 
        // @todo: check if we have data in our cache
        $cachedData = false; // @todo: get the actual value
 
        if(!empty($cachedData)) {
            $event->setParam('route-cached', true);
 
            $cachedRoute = \Zend\Mvc\Router\Http\Literal::factory($cachedData->getOptions());
            $router = $event->getRouter();
            $router->addRoute($cachedData->getRouteName(), $cachedRoute, 99999);
        }
    }

Saving the actual route match in the cache should be done in our routeSave method. We can use the var-cache service that you know from the “Learn ZF2″ book code. The final version of our routeSave method will look like this:

/**
     * Method that tries to save a route match into a cache system
     * @param MvcEvent $event
     */
    public function routeSave(MvcEvent $event)
    {
        $match = $event->getRouteMatch();
        if(!$match) {
            return;
        }
 
        if($event->getParam('route-cached') || !$event->getParam('route-cacheable')) {
            return;
        }
 
        $path = $event->getRequest()
                ->getUri()
                ->getPath();
 
        // save the route match into the cache.
        $cache = $event->getApplication()->getServiceManager()->get('var-cache');
        $cache->setItem('route-'.$path, $event->getRouteMatch());
    }

And the final version of the routeLoad method will look like this:

  /**
     * Method that tries to load cached routes and speed up the matching
     * @param MvcEvent $event
     */
    public function routeLoad(MvcEvent $event)
    {
        $request = $event->getRequest(); 
        if(!(
            $request->getMethod() == \Zend\Http\Request::METHOD_GET && 
            $request->getQuery()->count() == 0
            )) {
            // We do not cache route match for requests that can produce
            // different match.    
            return ;
        }
 
        $event->setParam('route-cacheable', true);
 
        // check if we have data in our cache
        $path = $event->getRequest()
                      ->getUri()
                      ->getPath();
 
        $cache = $event->getApplication()->getServiceManager()->get('var-cache');
        $cachedData = $cache->getItem('route-'.$path); 
 
        if(!empty($cachedData)) {
            $event->setParam('route-cached', true);
 
            $cachedRoute = \Zend\Mvc\Router\Http\Literal::factory($cachedData->getOptions());
            $router = $event->getRouter();
            $router->addRoute($cachedData->getRouteName(), $cachedRoute, 99999);
        }
    }

If you want to have this logic in a separate module then you can use as a base Trần Minh Quang’s Cache Route module. But beware: in the current version of Trần’s module saves route match for all types of HTTP requests. No matter if they are GET or POST requests and no matter if they have parameters or not. This may not function correctly for your application. The second thing that may not function correctly is the fact that the route match is stored during the MVC FINISH event. If you have Access Control List (ACL) logic which is called after routing and which changes the route match then saving the match at FINISH again will not work.

Happy ZF2 coding and good luck with improving the performance of your ZF2 application!

Plans for Zend Framework 3

Matthew Weier O’Phinney, the Zend Framework (ZF) Project Leader, announced the plans for the upcoming Zend Framework 3. Some of the things that are in the pipeline for Zend Framework 3 are:

  • Separating components into individual, versioned projects. This enables broader re-use and higher velocity of innovation.
  • Strong emphasis on HTTP messages, with Matthew leading the PSR-7 specification.
  • Updating our existing full-stack MVC framework to depend on the newly independent components for better reuse and simplicity. ZF2 MVC applications will have a documented upgrade path to ZF3 requiring minimal changes.
  • Embracing middleware runtime patterns as a lighter weight alternative to the enterprise MVC framework stack.
  • Enabling Apigility to work as a middleware stack, for better performance and simplicity, with the same streamlined, powerful user experience.
  • Optimizing for PHP 7, but supporting PHP 5.5 onwards.

In addition Andi Gutmans said: “The community has been looking for clear direction and we are giving it. “.

Stay tuned for more details and examples on the future Zend Framework 3.

Learn ZF2 is recommended from the Zend Framework Gods

If you haven’t seen these tweets take a look and see who is recommending the book:

Matthew Weier O’Phinney (ZF Project Lead – something like ZF2 God)

Evan Coury (ZF core dev team – sort of ZF2 semi-god)

Michelangelo van Dam (One of Europes’ most popular PHP evangelists)

Updated version of “Learn ZF2″.

I am happy to announce that “Learn ZF2″ has an updated version. The Kindle version is updated and published, soon to be followed by the paperback version (Amazon needs 3-7 days to update it in all stores), PDF and ePub versions.

This new version wouldn’t have been possible if it was not for the help, hints, recommendations and lines of code that our great contributors sent us. I would like to thank all of them: (in no particular order) Matt Comeione, Dmitry Oxman, Tim van Steenbergen, smozgur, Daniel Ventura, Matt X. Thanks a lot guys. You rock!

Zend Framework 2 is running on Intel Edison !

Intel Edison After some hours of compiling and tuning we are happy to announce that Zend Framework 2 and the Training Center application coming with the “Learn ZF2″ book is running on an Intel Edison, which is an embedded computer the size of a postage stamp. Below are some screenshots to prove this.

Now nothing can stop us from wearing our Zend Framework 2 application or carrying it everywhere with us :)




Themes in Zend Framework 2

Creating visual themes in your Zend Framework 2 (ZF2) application and switching between them is easier than you think.

I am often asked the following question:

How to create a completely separate mobile theme for our application? Not just using a new layout but also being able to override separate view templates, if needed.

I will extend this question to a more broader one:

How to create different themes in ZF2? Is that possible at all?

The short answer is: yes it is possible. In the next paragraphs I will show you one possible solution. Bear in mind that I will use some terms from the ZF2 world and you may need a bit of support from books like “Learn ZF2″ or the online manual to understand the explanations better. It might be a good idea to read also the previous article about View Models and Rendering.

The solution below will be created in a separate module called “Theme”. But you can quite happily make it part of your Application module.

First in out Module.php we need to attach an event listener that is executed before the actual rendering is happening. This can be done using the following code:

namespace Theme;

use Zend\Mvc\MvcEvent;


class Module
{
    public function onBootstrap(MvcEvent $event)
    {
        $eventManager = $event->getApplication()->getEventManager();
        $eventManager->attach(MvcEvent::EVENT_RENDER,array($this,'prepareTheme'),100);
    }
    //...
}

The import line above is that one:

$eventManager->attach(MvcEvent::EVENT_RENDER,array($this,'prepareTheme'),100);

It says attach the method prepareTheme from the current class to be executed with priority 100. 100 is higher than 1, which is the default priority and that guarantees us that prepareTheme will be executed before the actual rendering.

Before we jump to our prepareTheme method let’s see how we can define a “theme” and specify the view templates that have to be different in this theme. One possible solution is to use syntax which is similar to defining template_map and template_path_stacks in a view_manager section.

module//module.config.php

return array(
 // ...
 'themes' => array (
        'mobile' => array(
            'description' => 'Application Mobile Theme',
            'screenshot' => 'http://learnzf2.com/wp-content/uploads/2013/10/logo.png'
            'template_map' => array (
                'application/index/index' => __DIR__ .'/../theme/mobile/application/index/index.phtml',
                'layout/layout' => __DIR__ .'/../theme/mobile/layout.phtml',
            ),
            'template_path_stack' => array(
                __DIR__ . '/../theme/mobile/',
            ),
        )
    )

  //...
);

What we have above is a key themes in the configuration. Under that key there is another key with the unique name of the theme that we describe. In the case above this is mobile. And the array inside describes what we override. We used the same syntax for defining view templates as in the view_manager section and we added two more fields description and screenshot. The first has the short description for the theme and the second, which can be optional, points to the location from where our theme screenshot can be viewed.

In the prepareTheme method what we will do is check if there is a theme that is set and if yes we will try to instruct the ZF2 resolver to use the templates for that theme.

    
    public function prepareTheme(MvcEvent $event)
    {
        $services = $event->getApplication()->getServiceManager();
        $themes = $services->get('theme');
        $config = $services->get('config');

        // From our themes service we get the current theme that is set, if any
        $themeName = $themes->getName();
        if (!$themeName) {
               return;
        }

        $themeConfig = $themes->getConfig();
        $themeConfig = $themeConfig[$themeName];

        // !! Here Comes The Magic (R) !!

        // We override the template resolver
        // Here we add the changes that need to be applied to the existing template map
        if (isset($themeConfig['template_map'])) {
            $map = $services->get('ViewTemplateMapResolver');
            $map->merge($themeConfig['template_map']);
        }

        //  And we put our theme paths on top of the stack.
        //     This way if there is template in our theme it will be taken and used
        //     Otherwise we will use the ones provided earlier from the application
        if (isset($themeConfig['template_path_stack'])) {
            $stack = $services->get('ViewTemplatePathStack');
            $stack->addPaths($themeConfig['template_path_stack']);
        }
    }

And the above code is where most of the magic happens. Using that approach we can have themes that override the layout or selected templates and do not require us to override all existing application templates.

You can check the complete source code from here: https://github.com/slaff/learnzf2-theme. It contains all the tiny details needed to be able to switch between different themes and to decide which theme is the current one. If you use composer you can require the learnzf2/theme package.

A sample theme using that module can be found here (github, packagist: learnzf2/example-theme ).
Disclaimer: The sample theme is there to demonstrate the configuration and file structure needed to create a theme. It is very far from being good visual example. And we would be very happy if someone with good designer skills is willing to change that.

Enjoy :)

New ZF2 Topics Are Coming

Some time ago we asked our readers what topics they would like us to cover, that were not covered in the “Learn ZF2″ book. We got valuable feedback and thanks to it we started preparing some articles for the stuff that you are most interested. Starting from this week, actually from yesterday, we have new article that describes what view models are and how does the rendering works. The name of the article is “View Models and Rendering Demystified” and it will be followed by “Themes in ZF2 Application”, “One Action – Different Output Formats”, “Writing Command Line Apps In ZF2″ and more. We are thinking about bench marking ZF2 and Symphony 2, but for that we will need the help of someone from the Symphony 2 community.

Happy reading of “View Models and Rendering Demystified”.
Visit our site often to be the first to read the other articles.

View Models and Rendering Demystified

“View Models and Rendering Demystified” or the View Model is not your burger!

Often beginner Zend Framework 2  (ZF2) developers are confused about  view models and the whole rendering process.  In this article I will try to explain them making an analogy to ordering a burger at a restaurant. Bear in mind that I will use some terms from the ZF2 world and you may need a bit of support from books like “Learn ZF2″ or the online manual to understand the explanations better.

Imagine that you went to a restaurant and ordered cheese burger. In ZF2 terms this will mean that your browser made an HTTP request to a ZF2 application and as a path in the URL it had  /order/cheeseBurger. Also you are not in a hurry and want to eat your burger at the restaurant. The HTTP request for this may look like that:

GET /order/cheeseBurger/?takeAway=0
Host: zf2burger.com

Routing Event

In a real restaurant the person at the desk will check if there is cheese burger in the menu(hopefully he knows that) and if it can be ordered. In ZF2 this means that the router checks the routing rules and tries to map your URL to a valid routing definition.

In the module.config.php file for the Restaurant module you should have something like this:

'routes' => array(
            'ordercheeseburger' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/order/cheeseBurger',
                    'defaults' => array(
                        'controller' => 'Restaurant\Controller\Order',
                        'action'     => 'cheeseBurger',
                    ),
                ),
            ),
        )

Dispatching Event

The restaurant is offering your favorite cheese burger and the person at the desk instructs the kitchen to prepare your cheese burger. In ZF2 this means that from the routing definition the application has found a controller and action that is responsible for preparing cheese burgers. The action knows what recipe (view name) to use and what ingredients( view variables ) to include in order to prepare the cheese burger.  This is where our View Model  (VM) is created. Think of the VM as a box. Outside of the box stays a label saying the recipe name(view name) to be used and inside of the box we have the ingredients( variables) to use. Every ingredient is labeled (view variable name). At that moment we still do not have the burger ready.

After the box (VM) is ready the person at the desk asks you if you want to eat in the restaurant. If you say yes he opens another box and puts your box in it. In the big box he puts a label saying: decoration. In ZF2 one VM (box ) can have multiple children ( other boxes inside of it) or be part of the content of a bigger box.

namespace Restaurant\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class OrderController extends AbstractActionController
{	
   	public function cheeseBurgerAction() 
	{
		$viewModel = new ViewModel();
		$viewModel->setTemplate('cheeseburger-recipe');
	       	$viewModel->setVariables(array(
		          'cheese' => $gourmetFrenchCheese,
		          'meat'  => $mincedSuperbBeef,
		          'onions'=> $sweetOnion 
		));

		if($this->params('takeAway', false)) {
		           $viewModel->setTerminal(true);
		}
		
		return $viewModel;
	 }
}

One day in the kitchen came an important person who wanted to optimize the delivery time. He asked all persons working at the front desk to put all orders inside a new box and add a note with the start time when the order was accepted.  In ZF terms this can be a Debug module that adds debug overlay by creating a new view model (box ) and putting the content of the current box inside of it.

public function onBootstrap(MvcEvent $e)
{
	// Below is how we get access to the service manager
	$serviceManager = $e->getApplication()->getServiceManager();

        // ..
		
	$eventManager->attach(MvcEvent::EVENT_RENDER,
                                 array($this,'addDebugOverlay'),
                                 100);
}
    
    
public function addDebugOverlay(MvcEvent $event)
{
    	$viewModel = $event->getViewModel();
    	 
    	$sidebarView = new ViewModel();
    	$sidebarView->setTemplate('debug/layout/sidebar');
    	$sidebarView->addChild($viewModel, 'content');
    	 
    	$event->setViewModel($sidebarView);
}

(See the Learn ZF2 book repository on github)

Rendering Event

Once the person at the kitchen sees that someone ordered cheese burger he tries to apply the recipe instructions and use the ingredients that are given for that burger. He either knows the recipe or looks in cook books to find it. The books are stacked in a pile. The last book to be added is the first book to be read. When he looks in the books he uses the first recipe for cheese burger that he finds.  In ZF2 we have a resolver. The resolver tries to find from the view template name( recipe name)  the actual template file. And it can use either a template map (the cook remembers the recipe) or search for the view template name in a template path stack( multiple stacked cooking books with recipes until he finds the right one).

'view_manager' => array(
    'template_map' => array(
        'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
     ),
     'template_path_stack' => array(
          __DIR__ . '/../view',
      ),
),

One conclusion from the information above is that a fast cook is one who remembers a lot of recipes instead of looking in cooking books for them. In ZF2 application  looking for view template from a template map is much faster than trying to find the template in a template path stack. Which is important for the performance of your application especially when you have a lot of templates. ( In Learn ZF2 you will find detailed explanation in the Performance chapter)

The cook has all the ingredients and the right recipe. He starts opening the boxes until he reaches the innermost one. It contains information about your cheese burger. He cooks your juicy burger. Then he sees that there is a bigger box and he uses that information to add some salad in your plate as a decoration. In ZF2 the rendering process tries to render(cook) the innermost VM(box), and the content from the rendered VM is used in the parent VM. The decoration is stored in the layout template. The rendered content is the result from rendering the outermost VM.

Event Finish

Finally your burger is ready and served to you with a nice decoration. In ZF2 this is the moment where the rendered content is delivered to the browser.

Enjoy :)