Category Archives: Zend Framework 2

Sending attachments in multipart emails with Zend\Mail

I've written before about how to send an HTML email with a text alternative in Zend\Mail, but recently needed to send an attachment with my multipart email.

With help from various sources on the Internet, this is how to do it.

use Zend\Mail\Message;
use Zend\Mime\Message as MimeMessage;
use Zend\Mime\Part as MimePart;
use Zend\Mime\Mime;
use Zend\Mail\Transport\Sendmail;

function sendEmail($to, $from, $subject, $html, $text, $attachments = null)
{
    $message = new Message();

    $message->addTo($to);
    $message->addFrom($from);
    $message->setSubject($subject);

    // HTML part
    $htmlPart           = new MimePart($html);
    $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE;
    $htmlPart->type     = "text/html; charset=UTF-8";

    // Plain text part
    $textPart           = new MimePart($text);
    $textPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE;
    $textPart->type     = "text/plain; charset=UTF-8";

    $body = new MimeMessage();
    if ($attachments) {
        // With attachments, we need a multipart/related email. First part
        // is itself a multipart/alternative message        
        $content = new MimeMessage();
        $content->addPart($textPart);
        $content->addPart($htmlPart);

        $contentPart = new MimePart($content->generateMessage());
        $contentPart->type = "multipart/alternative;\n boundary=\"" .
            $content->getMime()->boundary() . '"';

        $body->addPart($contentPart);
        $messageType = 'multipart/related';

        // Add each attachment
        foreach ($attachments as $thisAttachment) {
            $attachment = new MimePart($thisAttachment['content']);
            $attachment->filename    = $thisAttachment['filename'];
            $attachment->type        = Mime::TYPE_OCTETSTREAM;
            $attachment->encoding    = Mime::ENCODING_BASE64;
            $attachment->disposition = Mime::DISPOSITION_ATTACHMENT;

            $body->addPart($attachment);
        }

    } else {
        // No attachments, just add the two textual parts to the body
        $body->setParts(array($textPart, $htmlPart));
        $messageType = 'multipart/alternative';
    }

    // attach the body to the message and set the content-type
    $message->setBody($body);
    $message->getHeaders()->get('content-type')->setType($messageType);
    $message->setEncoding('UTF-8');

    $transport = new Sendmail();
    $transport->send($message);
}

Let's look at some key parts.

The text of the email

The HTML and plain text parts of the email are treated the same. We create a Zend\Mime\Part and set the encoding and type. For HTML, it looks like this:

    $htmlPart           = new MimePart($html);
    $htmlPart->encoding = Mime::ENCODING_QUOTEDPRINTABLE;
    $htmlPart->type     = "text/html; charset=UTF-8";

Note that if your text is UTF-8, then you must set this in the part's type, as it is not passed through from the message level when set there.

Creating the multipart/alternative

If we have attachments, then we need to create a new multipart/alternative Zend\Mime\Message for the first part which contains the HTML and plain text parts:

        $content = new MimeMessage();
        $content->addPart($textPart);
        $content->addPart($htmlPart);

        $contentPart = new MimePart($content->generateMessage());
        $contentPart->type = "multipart/alternative;\n boundary=\"" .
            $content->getMime()->boundary() . '"';

        $body->addPart($contentPart);
        $messageType = 'multipart/related';

The important section here is that $contentPart is created with the generated message from the $content object which is the text of the two parts with the correct mime sections around them. We then need to set the type to mulipart/alternative and also define the boundary as the auto-generated identifier from $content.

Adding attachments

Attachments are similar to adding text:

            $attachment = new MimePart($thisAttachment['file_data']);
            $attachment->filename    = $thisAttachment['filename'];
            $attachment->type        = Mime::TYPE_OCTETSTREAM;
            $attachment->encoding    = Mime::ENCODING_BASE64;
            $attachment->disposition = Mime::DISPOSITION_ATTACHMENT;

            $body->addPart($attachment);

Again, you we create a Zend\Mime\Part for each attachment containing the raw file data. We also need to set the filename property along with the type, encoding and disposition.

Setting content-type

If we don't have attachments, then the message type is 'multipart/alternative', so that's set in the other half of the if/else. We can then add the body to the message and set its content-type:

    $message->setBody($body);
    $message->getHeaders()->get('content-type')->setType($messageType);
    $message->setEncoding('UTF-8');

We're all done, so we can send the email.

In this code, I'm using the Sendmail transport, but obviously Zend\Mail supports other options, including SMTP.

Integrating ZF2 forms into Slim

Let's say that you want to use Zend Framework 2's Form component outside of ZF2 itself. In this case, a Slim application.

It turns out that Composer makes this quite easy, though there's quite a lot of code involved, so this is a long article.

Start with a really simple Slim Application.

index.php:

require 'vendor/autoload.php';
$app = new \Slim\Slim();

$app->map('/', function () use ($app) {
    $app->render('home.php', array(
        'form' => $form
    ));
})->via('GET', 'POST');

$app->run();

templates/home.php:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
</head>

<body role="document">
  <div class="container">
    <h1 class="content-subhead">Using ZF2 Forms with Slim</h1>
  </div>
</body>
</html>

and of course, composer.json:

{
    "require": {
        "slim/slim": "2.*",
    }
}

Given this starting point, we can now add Zend\Form.

Firstly, we update composer.json:

{
    "require": {
        "slim/slim": "2.*",
        "zendframework/zend-form": "2.3.*",
        "zendframework/zend-servicemanager": "2.3.*",
        "zendframework/zend-i18n": "2.3.*",
        "zendframework/zend-view": "2.3.*",
        "zendframework/zend-escaper": "2.3.*"
    },
    "autoload": {
        "psr-4": {
            "RKA\\" : "RKA"
        }
    }
}

I've also set up the RKA namespace as we'll have to create some classes ourselves.

These are minimum set of components for it all to work. If you do your own rendering, then you don't need zend-view or zend-escaper. You'll then only need zend-i18n for some validators and filters.

When you run composer update, you'll see that the following ZF2 components are installed for you:

  • Zend\StdLib
  • Zend\Validator
  • Zend\Filter
  • Zend\InputFilter
  • Zend\Form
  • Zend\Loader
  • Zend\EventManager
  • Zend\View
  • Zend\I18n
  • Zend\ServiceManager
  • Zend\Escaper

Other than EventManger which is required by View, this doesn't seem an unreasonable list to me.

As, we'll probably want to override the default validation messages and add our own validators & filters, it's worth spending the time setting up our own instance of Zend\ServiceManager and using that to instantiate the form for us.

Configuring the service manager with the relevant plugin managers is usually done by Zend\Mvc & Zend\ModuleManager, so we'll simplify and create a single class called ServiceManagerConfigurator. This is the most complicated part of the entire thing and looks like this:

RKA\ServiceManagerConfigurator:

namespace RKA;

use Zend\ServiceManager\Config as ServiceConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ServiceManagerConfigurator
{
    // List of plugin managers to register (key name => class name)
    public $pluginManagers = [
        'FilterManager'      => 'Zend\Filter\FilterPluginManager',
        'FormElementManager' => 'Zend\Form\FormElementManager',
        'InputFilterManager' => 'Zend\InputFilter\InputFilterPluginManager',
        'ValidatorManager'   => 'Zend\Validator\ValidatorPluginManager',
        'ViewHelperManager'  => 'Zend\View\HelperPluginManager',
    ];
    
    // Keys to look for in the config array (config key name => sm key name)
    public $configKeys = [
        'validators'      => 'ValidatorManager',
        'filters'         => 'FilterManager',
        'form_elements'   => 'FormElementManager',
        'input_filters'   => 'InputFilterManager',
        'view_helpers'    => 'ViewHelperManager',
    ];

    // Main method that gives us back a configured ServiceManager
    public function createServiceManager(array $config)
    {
        // create ServiceManager
        $serviceManager = new ServiceManager();

        // set an initializer for ServiceLocatorAwareInterface
        $serviceManager->addInitializer(
            function ($instance, ServiceLocatorInterface $serviceLocator) {
                if ($instance instanceof ServiceLocatorAwareInterface) {
                    $instance->setServiceLocator($serviceLocator);
                }
            }
        );
        // add $serviceManger to the config keys so we can configure it
        $this->configKeys = ['service_manager' => $serviceManager] 
                            + $this->configKeys;

        // add the pluginManagers to the ServiceManager
        foreach ($this->pluginManagers as $key => $className) {
            $serviceManager->setInvokableClass($key, $className);
        }

        // add Zend\Form's view helpers to the ViewHelperManager
        $viewHelperManager = $serviceManager->get('ViewHelperManager');
        $vhConfig = new \Zend\Form\View\HelperConfig;
        $vhConfig->configureServiceManager($viewHelperManager);

        // apply configuration
        $this->applyConfig($serviceManager, $config);
        return $serviceManager;
    }

    // Helper method to apply config to the service manager and all plugins
    public function applyConfig($serviceManager, $config)
    {
        foreach ($this->configKeys as $keyName => $thisManager) {

            $smConfig = array();
            if (isset($config[$keyName]) && is_array($config[$keyName])) {
                $smConfig = $config[$keyName];
            }
            
            // Get this service manager from the main service manager if it
            // isn't an instance already
            if (!$thisManager instanceof ServiceManager) {
                $thisManager = $serviceManager->get($thisManager);
            }

            // Apply the config to this service manager
            $serviceConfig = new ServiceConfig($smConfig);
            $serviceConfig->configureServiceManager($thisManager);
        }
    }
}

I've commented the code so that hopefully, it's fairly easy to follow.

The key method is createServiceManager which will instantiate a ServiceManager object, add the plugin managers and other bits and bobs before calling the helper method applyConfig which will do the custom configuration.

To use it, we update our index.php:

index.php:

$app = new \Slim\Slim();

$config = [
    'validators' => array(
        'invokables' => array(
            'email-address' => 'RKA\Validator\EmailAddress',
            'string-length' => 'RKA\Validator\StringLength',
            // etc.
        ),
    ),
    'form_elements' => array(
        'invokables' => array(
            'RKA\ExampleForm'  => 'RKA\ExampleForm',
        ),
    ),

];

$smConfigurator = new RKA\ServiceManagerConfigurator();
$app->serviceManager = $smConfigurator->createServiceManager($config);
$app->view(new RKA\View());

// continue with $app->map() calls

We firstly set up the config that we want. In this case, I want to add my custom validators and then I add the form we want to use. Obviously, if we had custom filters, form elements or view helpers we could also add those too. Also, although I'm using invokables, you could also use factories if any of the classes you were adding had dependencies that needed adding.

The form is defined as any other Zend\Form is. In this case, I created a separate class called RKA\ExampleForm, but we could equally have used the factory creation.

RKA\ExampleForm:

namespace RKA;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;

class ExampleForm extends Form implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add([
            'name' => 'email',
            'options' => [
                'label' => 'Email address',
            ],
            'attributes' => [
                'id'       => 'email',
                'class'    => 'form-control',
                'required' => 'required',
            ],
        ]);

        $this->add([
            'name' => 'submit',
            'type' => 'button',
            'options' => [
                'label' => 'Go!',
            ],
            'attributes' => [
                'class' => 'btn btn-default',
            ],
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            'email' => [
                'required' => true,
                'filters'  => [
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                ],
                'validators' => [
                    ['name' => 'EmailAddress'],
                ],
            ],
        ];
    }
}

Nothing complicated is going on here. We add two elements to the form and then define some validation for the email element.

To use the form, we update our handler for '/':

$app->map('/', function () use ($app) {
    $formElementManager = $app->serviceManager->get('FormElementManager');
    $form = $formElementManager->get("RKA\ExampleForm");

    if ($app->request->isPost()) {
        $data = $app->request->post();
        $form->setData($data);
        $isValid = $form->isValid();
        if ($form->isValid()) {
            echo "Success!";
            exit;
        }
    }

    $app->render('home.php', array(
        'form' => $form
    ));
})->via('GET', 'POST');

We grab the $formElementManager from the service manager and then grab our example form form there. If the request is a POST, then we set the posted data to the form and test for validity. On success, we would probably redirect. Finally we render our view template, passing the form to it.

We render the form within the view template using Zend\Form's formRow view helpers to render the label, element and errors and label in one go for us, though we could have used the formLabel, formElement and formElementErrors view helpers separately:

templates/home.php:

    <!-- ... -->
    <h1 class="content-subhead">Using ZF2 Forms with Slim</h1>
      
      <form method="POST" role="form">
          <div class="form-group">
              <?= $this->formRow($form->get('email')) ?>
          </div>
          <?= $this->formElement($form->get('submit')) ?>
      </form>

    <!-- ... -->        
 

Finally, we have to extends Slim\View to integrate ZF2's view helper system with it. This requires implementing the Zend\View\Renderer\RendererInterface and also writing a __call() method:

Rka\View:

namespace RKA;

use Slim\Slim;
use Zend\View\Renderer\RendererInterface;

class View extends \Slim\View implements RendererInterface
{
    protected $viewHelpers;
    protected $helperCache;

    public function __construct()
    {
        parent::__construct();

        $app = Slim::getInstance();
        $sm = $app->serviceManager;
        $this->viewHelpers = $sm->get('ViewHelperManager');
    }

    public function plugin($name, array $options = null)
    {
        $helper = $this->viewHelpers->get($name, $options);
        $helper->setView($this);
        return $helper;
    }

    public function __call($method, $argv)
    {
        if (!isset($this->helperCache[$method])) {
            $this->helperCache[$method] = $this->plugin($method);
        }
        if (is_callable($this->helperCache[$method])) {
            return call_user_func_array($this->helperCache[$method], $argv);
        }
        return $this->helperCache[$method];

    }

    // Required by RendererInterface
    public function render($template, $data = null)
    {
        return parent::render($template, $data);
    }

    public function getEngine()
    {
        return $this;
    }
    
    public function setResolver(\Zend\View\Resolver\ResolverInterface $resolver)
    {
        return $this;
    }
}

One undocumented feature of the formElement view helper is that it looks for a method called plugin in the View class, so I created one for it!

The final result looks like:

Slim zendform

That's it. The code that's shown here is on GitHub in the slim-zendform project, so you can download it and play around yourself.

Globally overriding validation messages for ZF2 forms

One thing that I always do when creating a Zend Framework 2 form is override the validation messages for a number of validators – EmailAddress in particular.

I recently decided that I should probably sort this one out once and be done with it. Turns out that it's quite easy assuming that you use the FormElementManger to instantiate your forms.

All that I need to do is create my own validator classes that extend the Zend Framework ones and just set new message templates. This is what EmailAddress looks like:

namespace RKA\Validator;

use Zend\Validator\EmailAddress as BaseEmailAddress;

class EmailAddress extends BaseEmailAddress
{
    protected $messageTemplates = array(
        self::INVALID            => "Invalid type given. String expected",
        self::INVALID_FORMAT     => "Invalid email address",
        self::INVALID_HOSTNAME   => "Invalid email address",
        self::INVALID_MX_RECORD  => "Invalid email address",
        self::INVALID_SEGMENT    => "Invalid email address",
        self::DOT_ATOM           => "Invalid email address",
        self::QUOTED_STRING      => "Invalid email address",
        self::INVALID_LOCAL_PART => "Invalid email address",
        self::LENGTH_EXCEEDED    => "Email address is too long",
    );
}

To ensure that the ValidatorPluginManager uses the new validator in place of the default one, we override in module.config.php:

    'validators' => [
        'invokables' => [
            'email-address' => 'RKA\Validator\EmailAddress',
            'string-length' => 'RKA\Validator\StringLength',
            // etc.
        ],
    ],

Now, whenever you create a form with input filter that uses FormAbstractServiceFactory or via a config array with the Form\Factory as set up in my last post, then the new validator is picked up (assuming you use the short name).

If you create your forms via a class, then you should use the FormElementManager like this.

Firstly, create your form:

namespace RKA\Form;

use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;

class ExampleForm extends Form implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add([
            'name' => 'email',
            'type' => 'text',
            'options' => [
                'label' => 'Email address',
            ],
            'attributes' => [
                'class'    => 'form-control',
                'required' => 'required',
            ],
        ]);

        $this->add([
            'name' => 'submit',
            'type' => 'button',
            'options' => [
                'label' => 'Go!',
            ],
            'attributes' => [
                'class' => 'btn btn-default',
            ],
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            'email' => [
                'required' => true,
                'filters'  => [
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                ],
                'validators' => [
                    ['name' => 'EmailAddress'],
                ],
            ],
        ];
    }
}

Note that in getInputFilterSpecification we use the service manager key names for each validator name. The ones that are shipped with ZF2 are listed in the ValidatorPluginManager.

Now register it with the FormElementManager in module.config.php:

    'form_elements' => [
        'invokables' => [
            'RKA\ExampleForm'  => 'RKA\Form\ExampleForm',
        ],
    ],

You can then instantiate it in your controller via the main service locator:

    // in my controller
    public function getExampleForm()
    {
        $formManager = $this->getServiceLocator()->get('FormElementManager');
        return $formManager->get('RKA\ExampleForm');
    }

In addition to forms, you can also register your own fieldsets and elements under the 'form_elements' key and then use them within your forms.

Creating a ZF2 form from config

I have a requirement to create a Zend\Form from a dynamically created array which means that I can't use the FormAbstractServiceFactory and so will use Zend\Form\Factory directly.

If you need to override any form elements, validators or add new ones, then you'll need the correct plugin managers for the factory. The way to set this up is like this:

$factory = new Factory();
$formElements = $this->serviceLocator->get('FormElementManager');
$factory->setFormElementManager($formElements);
$inputFilters = $this->serviceLocator->get('InputFilterManager');
$factory->getInputFilterFactory()->setInputFilterManager($inputFilters); 

Your $factory is now configured and so you can create a form like this:

$formConfig = [
    'elements' => [
        [
            'spec' => [
                'name' => 'name',
                'options' => [
                    'label' => 'Your name',
                ],
                'attributes' => [
                    'type' => 'text',
                    'class' => 'form-control',
                    'required' => 'required',
                ],
            ],
        ],
        [
            'spec' => [
                'name' => 'email',
                'options' => [
                    'label' => 'Your email address',
                ],
                'attributes' => [
                    'type' => 'text',
                    'class' => 'form-control',
                    'required' => 'required',
                ],
            ],
        ],
    ],
    'input_filter' => [
        'name' => [
            'name'       => 'name',
            'required'   => true,
            'validators' => [
                [
                    'name' => 'not_empty',
                ],
                [
                    'name' => 'string_length',
                    'options' => [
                        'max' => 30,
                    ],
                ],
            ],
        ],
        'email' => [
            'name'       => 'email',
            'required'   => true,
            'validators' => [
                [
                    'name' => 'not_empty',
                ],
                [
                    'name' => 'email_address',
                ],
            ],
        ],
    ],
];

$form = $factory->createForm($formSpec);

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!