Category Archives: Zend Framework 2

Injecting dependencies into your ZF2 controllers

When starting working with Zend Framework 2, it’s common to copy the skeleton and put your controller definitions in module.config.php like this:

'controllers' => array(
  'invokables' => array(
    'Application\Controller\Index' => 'Application\Controller\IndexController',
    'Application\Controller\Blog' => 'Application\Controller\BlogController',
  ),
),

The controllers keyword is picked up by the ControllerManager which is an instance of the ServiceManager which means that it creates the controller instance for you when the dispatcher needs it.
Continue reading

Investigating Apigility

At ZendCon 2013, Zend announced Apigility which is intended to ease the creation of APIs.

It consists of these things:

  • A set of ZF2 modules that do the heavy lifting of creating an API
  • A application wrapper for creating standalone web API applications
  • A built-in administration website for use in development to define the API

Rather nicely, it supports REST and RPC and deal with error handling, versioning & content negotiation for you.

Getting started from nothing to create a new API is quite easy: you simply install the Apigility Skeleton Application following the the instructions in the README and then create your new APIs using the admin application.

The more interesting case for me is how to use Apigility to supply an API to an existing application, so I’m going to explore how to do this.

Let’s start by adding an API to the ZF2 tutorial application.

Install the tutorial application

The easiest way to do this is to use my zf2-tutorial-to-go GitHub repository. Just follow the instructions in the README to set it up. (Make sure that you use PHP 5.4+ so you can use the built in web server.)

Add Apigility

To add Apigility to our application we update composer.json:

We set the minimum stability and add a new repository and then add some packages to the require and require-dev sections.

Update the entire composer.json so that it contains:

{
    "name": "akrabat/zf2-tutorial-apigility",
    "description": "ZF2 Tutorial with Apigility",
    "license": "BSD-3-Clause",
    "minimum-stability": "dev",
    "repositories": [
        { "type": "composer",
          "url": "https://packages.zendframework.com" }
    ],
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "dev-develop",
        "zfcampus/zf-apigility": "dev-master"
    },
    "require-dev": {
        "zendframework/zftool": "dev-master",
        "zendframework/zend-developer-tools": "dev-master",
        "zfcampus/zf-apigility-admin": "dev-master"
    }
}

and run composer:

$ php composer.phar update

Once it’s finished, we can update our application.

Update the application

There are a number of things we need to do to update our application to support Apigility.

Firstly, we need to add the modules, but some of them are development only and not intended for production. One way to do support this is to provide a separate config/development.config file that is only loaded if it exists and then ensure that this file isn’t deployed to production.

To support this, we modify public/index.php:

Replace:

// Run the application!
ZendMvcApplication::init(require 'config/application.config.php')->run();

with

$appConfig = require 'config/application.config.php';
if (file_exists('config/development.config.php')) {
    $appConfig = Zend\Stdlib\ArrayUtils::merge($appConfig, require 'config/development.config.php');
}

// Run the application!
ZendMvcApplication::init($appConfig)->run();

As you can see, this simply checks for the presence of development.config.php and merges it.

We can now add our modules. Firstly add the following modules to config/application.config.php:

    'ZF\Apigility',
    'AssetManager',
    'ZF\ApiProblem',
    'ZF\Hal',
    'ZF\ContentNegotiation',
    'ZF\Rest',
    'ZF\Configuration',
    'ZF\Versioning',

And then create config/development.config.php with the following contents:

< ?php
// NOTE: DO NOT deploy this file to LIVE

return array(
    'modules' => array(
        'ZFTool',
        'ZF\Apigility\Admin',
    ),
);

Within Apigility, the DB-Connected functionality relies on ZF2′s Adapter\AbstractServiceFactory, so we need to register this with the Service Manager. The easiest place to do this is in config/autoload/global.php.

Update config/global.php and add:

        'abstract_factories' => array(
           'Zend\\Db\\Adapter\\AdapterAbstractServiceFactory',
        ),

within the 'service_manager' array.

As we’re in development mode, we can now start up Apigility’s admin system and define an API for our albums. From the command line, in the application’s base directory you can start the PHP internal web server using:

$ php -S 0.0.0.0:8080 -t public/ public/index.php

and then navigate to http://localhost:8080 to see a list of albums and http://localhost:8080/admin to see the Apigility admin:

Apiligity admin

Use Apigility admin to create an API

The simplest API to create with Apigility is a DB-Connected one. In this model, we tell Apigility how to connect to our database and it generates an API for us.

The first part of this to create a database adapter for apigility’s use.

Click on the Database Adapters link on the admin dashboard and press the Create New DB Adapter button.

Enter the following into the Create new DB Adapter form:

  • Adapter Name: DBAlbum
  • Driver Type: Pdo_Sqlite
  • Database: data/album.sqlite

And then press the Create Db Adapter button to create our new adapter.

We can now create an API, so press the Create New API button in the top right of the admin. Enter AlbumApi into the form field and press the Create API button.

This creates version 1 of our API and we now need to create a service. We will create a REST service, so click on the REST Services link and then click the Create New REST Service button.

Choose the DB-Connected tab and then fill out the form:

  • DB Adapter Name: DBAlbum
  • Table Name: album

Press the Create DB-Connected REST Service button to create the service.

We now need to set our new service up correctly, so click on the AlbumApiV1RestAlbumAlbumEntity link and select the Edit tab.

Firstly we update the route as we already have a /album route. Update Route to match to be /api/album[/:id]

Also, in the REST Parameters section change the Identifier Name from album_id to id.

Finally, press the Save button.

(Note that I had trouble with the Identifer Name reverting back to to album_id, so I edited module/AlbumApi/config/module.config.php and replaced all instances of album_id with id and that solved it. I assume that this is a minor bug somewhere in the admin system.)

You can also edit other aspects of your API here. I particularly like that you can choose which HTTP verbs that your API responds to, so to make a read-only API, you could simply uncheck all verbs other than GET.

Test your new API

I use curl on the command line for testing things:

curl -s -H "Accept: application/json" 
http://localhost:8080/api/album | python -mjson.tool

And you should see a nicely formatted list of albums with HAL links.

GitHub repository

I have created a GitHub repository called zf2-tutorial-apigility which contains the code. The master branch is the fully working DB-connected API and the base tag is at the point before we used the Apigility admin system.

Conclusion

Apigility doesn’t seem too hard to integrate into an existing ZF2 application after all. I particularly like that the admin system is development only and the code it creates can be checked into your VCS for easy inspection of what it has done.

Obviously a DB-connected resource is a very simply integration, but I’m pleased to see that Apigility has provided an AlbumCollection.php and an AlbumEntity.php file with the AlbumApi module to allow for customisation.

Given that Apigility was announced at ZendCon, I’m fairly confident that more work will be done the project and I look forward to new features such as authentication and validation that are sure to be coming soon.

Returning JSON errors in a ZF2 application

If you have a standard ZF2 application and accept application/json requests in addition to application/html, then you have probably noticed that when an error happens, HTML is created, even though the client has requested JSON.

One way to fix this is to create a listener on MVC’s render event to detect that an error has occurred and substitute a JsonModel in place of the ViewModel.

The easiest way to do this in your ApplicationModule.

Firstly, attach a lister to render:

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        // attach the JSON view strategy
        $app      = $e->getTarget();
        $locator  = $app->getServiceManager();
        $view     = $locator->get('ZendViewView');
        $strategy = $locator->get('ViewJsonStrategy');
        $view->getEventManager()->attach($strategy, 100);

        // attach a listener to check for errors
        $events = $e->getTarget()->getEventManager();
        $events->attach(MvcEvent::EVENT_RENDER, array($this, 'onRenderError'));
    }

Now write the error detector:

    public function onRenderError($e)
    {
        // must be an error
        if (!$e->isError()) {
            return;
        }

        // Check the accept headers for application/json
        $request = $e->getRequest();
        if (!$request instanceof HttpRequest) {
            return;
        }

        $headers = $request->getHeaders();
        if (!$headers->has('Accept')) {
            return;
        }

        $accept = $headers->get('Accept');
        $match  = $accept->match('application/json');
        if (!$match || $match->getTypeString() == '*/*') {
            // not application/json
            return;
        }

        // make debugging easier if we're using xdebug!
        ini_set('html_errors', 0); 

        // if we have a JsonModel in the result, then do nothing
        $currentModel = $e->getResult();
        if ($currentModel instanceof JsonModel) {
            return;
        }

        // create a new JsonModel - use application/api-problem+json fields.
        $response = $e->getResponse();
        $model = new JsonModel(array(
            "httpStatus" => $response->getStatusCode(),
            "title" => $response->getReasonPhrase(),
        ));

        // Find out what the error is
        $exception  = $currentModel->getVariable('exception');

        if ($currentModel instanceof ModelInterface && $currentModel->reason) {
            switch ($currentModel->reason) {
                case 'error-controller-cannot-dispatch':
                    $model->detail = 'The requested controller was unable to dispatch the request.';
                    break;
                case 'error-controller-not-found':
                    $model->detail = 'The requested controller could not be mapped to an existing controller class.';
                    break;
                case 'error-controller-invalid':
                    $model->detail = 'The requested controller was not dispatchable.';
                    break;
                case 'error-router-no-match':
                    $model->detail = 'The requested URL could not be matched by routing.';
                    break;
                default:
                    $model->detail = $currentModel->message;
                    break;
            }
        }

        if ($exception) {
            if ($exception->getCode()) {
                $e->getResponse()->setStatusCode($exception->getCode());
            }
            $model->detail = $exception->getMessage();

            // find the previous exceptions
            $messages = array();
            while ($exception = $exception->getPrevious()) {
                $messages[] = "* " . $exception->getMessage();
            };
            if (count($messages)) {
                $exceptionString = implode("n", $messages);
                $model->messages = $exceptionString;
            }
        }

        // set our new view model
        $model->setTerminal(true);
        $e->setResult($model);
        $e->setViewModel($model);
    }

You’ll also need some use statements:

use ZendHttpRequest as HttpRequest;
use ZendViewModelJsonModel;
use ZendViewModelModelInterface;

(This code is heavily inspired from PhlyRestfully – Thanks Matthew!)

Essentially, we check that we are in an error situation and that the client wants JSON. If we are, we create a JsonModel and populate it information. I’ve used the fields from the draft Problem Details for HTTP APIs IETF spec as it seems sensible to do so.

Note though, that if you run this, you’ll see that the response’s content-type is application/json, not application/api-problem+json. You can’t set this in onRenderError though as the view’s JSON strategy will override it.

A brute-force solution to this is to override the content-type header in a listener on the finish event. Firstly we update onBootstrap():

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        // ...
        $events->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
    }

Then we write the onFinish listener:

    public function onFinish($e)
    {
        $response = $e->getResponse();
        $headers = $response->getHeaders();
        $contentType = $headers->get('Content-Type');
        if (strpos($contentType->getFieldValue(), 'application/json') !== false
            && strpos($response->getContent(), 'httpStatus')) {
            // This is (almost certainly!) an api-problem
            $headers->addHeaderLine('Content-Type', 'application/api-problem+json');
        }
    }

This method simply looks at the response and tries to guess if it’s an api-problem. If it is, then it changes the content-type in the Response’s header.

Now you’re done.

You can test with curl:

$ curl -s -i -H "Accept: application/json" "http://localhost/booklist/public/invalid" 

which will return:

HTTP/1.1 404 Not Found
Date: Mon, 09 Sep 2013 08:55:01 GMT
Server: Apache/2.2.22 (Unix) DAV/2 PHP/5.4.19 mod_ssl/2.2.22 OpenSSL/0.9.8x
X-Powered-By: PHP/5.4.19
Content-Length: 100
Content-Type: application/api-problem+json

{"httpStatus":404,"title":"Not Found","detail":"The requested URL could not be matched by routing."} 

Of course, in an ideal world, someone would package this up into a module :)

Configuring a ZF2 view helper before rendering

The currencyFormat view helper is very easy to use:

echo $this->currencyFormat($value, 'GBP', 'en_GB');

When I was reading the documentation for the currencyFormat view helper, I discovered that you could configure the currency code and locale once rather than in every call:

// Within your view script
$this->plugin("currencyformat")->setCurrencyCode("GBP")->setLocale("en_GB");

This is obviously useful, but even more useful would be if we could set it once by default and then override if we need to in a specific call.

The easiest way to do this is to use an event listener on the renderer.post View event within a modules’s onBootstrap method, like this:

namespace Application;

use ZendViewViewEvent;
use ZendViewRendererPhpRenderer;
class Module
{
    public function onBootstrap($e)
    {
        $events = $e->getApplication()->getEventManager();
        $sharedEvents = $events->getSharedManager();
        $sharedEvents->attach('ZendViewView', ViewEvent::EVENT_RENDERER_POST, function($event) {
            $renderer = $event->getRenderer();
            if ($renderer instanceof PhpRenderer) {
                $renderer->plugin("currencyformat")->setCurrencyCode("GBP")->setLocale('en_GB');
            }
        });
    }
    // ...
}

Now we can simply do call currencyFormat() with the value in our view script:

echo $this->currencyFormat($value);

Returning a ZF2 HydratingResultSet when starting with raw SQL

If you’re using Zend Framework 2′s ZendDb and want to write raw SQL that returns a HydratingResultSet, then you can do this:

use ZendDbAdapterAdapterAwareInterface;
use ZendDbAdapterAdapter;
use ZendStdlibHydratorArraySerializable;
use ZendDbResultSetHydratingResultSet;
use ZendDbAdapterDriverResultInterface;

use MyEntityMyEntity;  // some sort of entity object

class MyMapper implements AdapterAwareInterface
{
    protected $dbAdapter;

    public function fetchRowsWithValue($fieldValue)
    {
        $sql = "SELECT * FROM my_table_name
                WHERE my_field_name = ?
                ";

        $params = array(
            $fieldValue,
        );

        return $this->fetch($sql, $params);
    }

    protected function fetch($sql, $params = null)
    {
        $dbAdapter = $this->getDbAdapter();
        $statement = $dbAdapter->createStatement($sql);
        $statement->prepare();

        $result = $statement->execute($params);

        if ($result instanceof ResultInterface) {
            $hydrator     = new ArraySerializable();
            $rowPrototype = new MyEntity();
            $resultset    = new HydratingResultSet($hydrator, $rowPrototype);
            $resultset->initialize($result);

            return $resultset;
        }

        return $result;
    }

    // Don't forget to implement getDbAdapter() and setDbAdapter()!

}

When you iterate over the HydratingResultSet, you get a populated instance of MyEntity. The $hydrator instance is the object that knows how to populate your entity object. In my case, I’ve used an ArraySerializable hydrator, but there’s other options available, or you can write your own.

As an aside, if you don’t want hydrated entities, then the $result that’s returned from the statement’s execute() call is an instance of ZendDbAdapterDriverResultInterface which means that you can iterate over it and get an array for each row in the result set.

Displaying the generated SQL from a ZendDbSql object

If you use ZendDbSql to generate your SQL, then it’s useful to find out what the generated SQL looks like.

Consider code like this:

public function fetchAllWithTitleSince($title, $since)
{
    $sql = new Sql($this->dbAdapter);

    $select = $sql->select();
    $select->from($this->tableName);
    $select->columns(array('id', 'title', 'url', 'date_updated'));
    $select->where->like('title', "%$title%");
    $select->where->greaterThanOrEqualTo('date_created', date('Y-m-d', strtotime($since)));

    $statement = $this->dbAdapter->createStatement();
    $select->prepareStatement($this->dbAdapter, $statement);
    return $statement->execute();
}

To find out what the generated SQL will look like, you can use the $select‘s getSqlString() method:

$select->getSqlString();

For me, this results in lots of warnings along the lines of:

Lots of notices

This is less than helpful, so to avoid the warnings, you need to supply the correct platform information to the method:

    $select->getSqlString($this->dbAdapter->getPlatform());

So we now get:

Expected SQL string

This is much better!

IN and ZendDbSql’s where()

This is a short note to myself. ZendDbSql objects allow you to do this:

$id = 2;
$select->where(array('id' => $id));

which generates the (My)SQL:

WHERE `id` = '2'

If you want the SQL generated to use the IN operator, then just pass in an array:

$idList = array(1, 3, 4);
$select->where(array('id' => $idList));

which generates:

WHERE `id` IN ('1', '3', '4')

This obviously also works for ZendDbSql‘s update() as well as select().

Caching your ZF2 merged configuration

Zend Framework 2′s ModuleManager has the ability to cache the merged configuration information for your application. This is very useful as it allows you to separate out your configuration within the config/autoload directory into logical files without worrying about the performance implications of lots of files.

Enabling caching is simply a case of setting these configuration keys in config/application.config.php within the module_listener_options section:

	'module_listener_options' => array(
        'config_cache_enabled'     => true,
        'module_map_cache_enabled' => true,
        'cache_dir'                => 'data/cache/',
		// other keys go here (e.g. module_paths & config_glob_paths)
	),

This then creates the cache files data/cache/module-classmap-cache.php and data/cache/module-config-cache.php and you’re done. If you need to regenerate the files, simply delete then.

During development this can be a pain to remember!

We can solve this by only caching when in production. The easiest way to do this is by setting an environment variable in your virtual host. For Apache, use SetEnv; you’re on your own for any other web server.

The way I do this is to modify config/application.config.php like this:

use ZendStdlibArrayUtils;

$config = array(
	// all standard application configuration ...
);


$localAppConfigFilename = 'config/application.config.' . getenv('APPLICATION_ENV') . '.php';
if (is_readable($localAppConfigFilename)) {
    $config = ArrayUtils::merge($config, require($localAppConfigFilename));
}

return $config;

I then create a separate configuration file for each environment. For production, I turn on caching:

config/application.config.production.php:

return array(
    'module_listener_options' => array(
        'config_cache_enabled' => true,
        'module_map_cache_enabled' => true,
    ),
);

and for my local development, I add some development modules:

config/application.config.development.php:

return array(
    'modules' => array(
        'BjyProfiler',
        'ZendDeveloperTools',
    ),
);

This system allows me to have a faster production site and specific modules loaded when developing that make life easier.

Injecting configuration into a ZF2 controller

One thing you may find yourself needing to do is access configuration information in a controller or service class.

The easiest way to do this is to use the ServiceManger‘s initialiser feature. This allows you to write one piece of injection code that can be applied to multiple objects. It’s easier to show this in action!

Let’s assume that we have this configuration file:

config/autoload/global.php:

return array(
    'application' => array(
        'setting_1' => 234,
    )
);

That is, we have a key called 'application' that contains application-specific configuration that we would like to access in our controllers (or service classes).

Firstly we define a interface, ConfigAwareInterface:

module/Application/src/Application/ConfigAwareInterface.php:

namespace Application;

interface ConfigAwareInterface
{
    public function setConfig($config);
}

We can now add this to a controller:

module/Application/src/Application/Controller/IndexController.php:

namespace ApplicationController;

use ZendMvcControllerAbstractActionController;
use ZendViewModelViewModel;
use ApplicationConfigAwareInterface;

class IndexController extends AbstractActionController
    implements ConfigAwareInterface
{
    protected $config;

    public function setConfig($config)
    {
        $this->config = $config;
    }

    // action methods, etc.
}

In the controller, we add a use statement, implement our interface and the required setConfig() method.

Finally, we add an initializer to the Module class:

module/Application/Module.php:

class Module
{
    // Other methods, such as OnBoostrap(), getAutoloaderConfig(), etc.

    public function getControllerConfig()
    {
        return array(
             'initializers' => array(
                function ($instance, $sm) {
                    if ($instance instanceof ConfigAwareInterface) {
                        $locator = $sm->getServiceLocator();
                        $config  = $locator->get('Config');
                        $instance->setConfig($config['application']);
                    }
                }
            )
        );
    }
}

(We also have a use ApplicationConfigAwareInterface; statement at the top!)

As getControllerConfig() is used by a specific ServiceManager only for controllers, we need to retrieve the main ServiceManager using getServiceLocator() in order to collect the merged configuration. As we only want the settings from within the 'application' key, we only pass that into the controller’s setConfig() method.

The configuration settings are now available to us in any controller that implements ConfigAwareInterface.

We can also do this for service classes – we simply add another initalizer to the Module:

module/Application/Module.php:

    public function getServiceConfig()
    {
        return array(
             'initializers' => array(
                function ($instance, $sm) {
                    if ($instance instanceof ConfigAwareInterface) {
                        $config  = $sm->get('Config');
                        $instance->setConfig($config['application']);
                    }
                }
            )
        );
    }

It also follows that you can use initializers for any type of generic injection, such as mappers, db adapters, loggers, etc. Simply create an interface and write an initalizer.

Simple logging of ZF2 exceptions

I recently had a problem with a ZF2 based website where users were reporting seeing the error page displayed, but I couldn’t reproduce in testing. To find this problem I decided to log every exception to a file so I could then go back and work out what was happening. In a standard ZF2 application, the easiest way to do this is to add a listener to the ‘dispatch.error’ event and log using ZendLog.

To do this, I started with the Application’s Module class and added an event listener:

    public function onBootstrap($e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $eventManager->attach('dispatch.error', function($event){
            $exception = $event->getResult()->exception;
            if ($exception) {
                $sm = $event->getApplication()->getServiceManager();
                $service = $sm->get('ApplicationServiceErrorHandling');
                $service->logException($exception);
            }
        });
    }

This code attaches an anonymous function to the ‘dispatch.error’ event which retrieves the exception from the event’s result and passes it to the logException() method in an ErrorHandling class. We retrieve ErrorHandling from the service manager which allows us to inject an instance of ZendLog into it:

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'ApplicationServiceErrorHandling' =>  function($sm) {
                    $logger = $sm->get('ZendLog');
                    $service = new ErrorHandlingService($logger);
                    return $service;
                },
                'ZendLog' => function ($sm) {
                    $filename = 'log_' . date('F') . '.txt';
                    $log = new Logger();
                    $writer = new LogWriterStream('./data/logs/' . $filename);
                    $log->addWriter($writer);

                    return $log;
                },
            ),
        );
    }

There’s obviously a few use statements at the top of the file for this to work:

use ApplicationServiceErrorHandling as ErrorHandlingService;
use ZendLogLogger;
use ZendLogWriterStream as LogWriterStream;

The logging itself is done within the ErrorHandling class:

namespace ApplicationService;

class ErrorHandling
{
    protected $logger;

    function __construct($logger)
    {
        $this->logger = $logger;
    }

    function logException(Exception $e)
    {
        $trace = $e->getTraceAsString();
        $i = 1;
        do {
            $messages[] = $i++ . ": " . $e->getMessage();
        } while ($e = $e->getPrevious());

        $log = "Exception:n" . implode("n", $messages);
        $log .= "nTrace:n" . $trace;

        $this->logger->err($log);
    }
}

The logException method simply creates a string containing the exception’s message along with any previous exception messages and the trace. We then call the Log‘s err method to store the log and can peruse at our leisure.

Update: I have updated the logException method to use a do..while() loop as it’s neater and doesn’t cause a an out-of-memory error that the previous code did. That is, it’s a good idea to reuse the same variable when calling getPrevious()!