Setting up mailcatcher as a service in Debian/Ubuntu

I've recently been changing joind.in's Vagrant system to use Debian and one issue I came across was getting Mailcatcher to start on boot and integrate property with the service command.

To do this, I created an init script which is based off the skeleton and then stored this in /etc/init.d and then ran update-rc.d mailcatcher defaults to set up the correct links in the various rc.d directories.

This is the init script:

/etc/init.d/mailcatcher:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          mailcatcher
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/rvm/gems/ruby-1.9.3-p547/bin
DESC="Super simple SMTP server"
NAME=mailcatcher
DAEMON=/usr/local/rvm/wrappers/default/$NAME
DAEMON_ARGS=" --http-ip 0.0.0.0"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
        || return 1
    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
        $DAEMON_ARGS \
        || return 2
    # Add code here, if necessary, that waits for the process to be ready
    # to handle requests from services started subsequently which depend
    # on this one.  As a last resort, sleep for some time.
     
    # Create the PIDFILE
    pidof mailcatcher >> $PIDFILE
}

#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    
    if [ -f "$PIDFILE" ]
    then
        kill `cat $PIDFILE`
        rm -f $PIDFILE
        return 0
    else
        return 1
    fi
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
    #
    # If the daemon can reload its configuration without
    # restarting (for example, when it is sent a SIGHUP),
    # then implement that here.
    #
    start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
    return 0
}

case "$1" in
  start)
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  stop)
    [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  status)
    status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
    ;;
  #reload|force-reload)
    #
    # If do_reload() is not implemented then leave this commented out
    # and leave 'force-reload' as an alias for 'restart'.
    #
    #log_daemon_msg "Reloading $DESC" "$NAME"
    #do_reload
    #log_end_msg $?
    #;;
  restart|force-reload)
    #
    # If the "reload" option is implemented then remove the
    # 'force-reload' alias
    #
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
            0) log_end_msg 0 ;;
            1) log_end_msg 1 ;; # Old process is still running
            *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
        # Failed to stop
        log_end_msg 1
        ;;
    esac
    ;;
  *)
    #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
    exit 3
    ;;
esac

:

As you can probably tell, it's very obviously a tweaked version of /etc/init.d/skeleton, but there are some important changes:

Path to binary
As Mailcatcher is a ruby app, the correct path to the binary is actually /usr/local/rvm/wrappers/default/mailcatcher which is not where which tells you it is. I suspect that my lack of knowledge about Ruby environments is showing…

do_start()
The do_start() function calls through to start-stop-daemon to start mailcatcher. However this doesn't create a pid file in /var/run for us, so we create it ourselves using:

pidof mailcatcher >> $PIDFILE

do_stop()
Mailcatcher is intended to be stopped by pressing the Quit button in the HTML interface, so the default code in skeleton doesn't work. I rewrote it to simply kill the process if the pid file exists:

    if [ -f "$PIDFILE" ]
    then
        kill `cat $PIDFILE`
        rm -f $PIDFILE
        return 0
    else
        return 1
    fi

That's it. The most important thing about these changes is that service mailcatcher status now works as expected and so Puppet's ensure => 'running' test actually works correctly.

Codes of conduct

As my mind turns towards the conferences that I'm attending this autumn, I came across Why you want a code of conduct & how we made one by Erin Kissane. I highly recommend that you read it and the links within it.

The part that struck me most was the thoughts on a plan of action. It's all very well to have a code of conduct, but if the event's organisers haven't got a plan on how to deal with reports and actual enforce the code, then it's a waste of time and doesn't help anyone.

I can imagine that reporting, in particular, can be more complicated than initial thoughts would imply. "Just talk to a member of staff" doesn't work very well for everyone as they always seem busy.If they aren't busy, then they are usually surrounded by people; not everyone is confident enough to interrupt and say "can I have word, privately". I can see why the really good codes of conduct have a telephone number on them. One code of conduct I read says that the report should preferably be in writing. That may be useful legally, but I'm not sure that that sends the right message.

Similarly, I wonder if the organisers with a code of conduct have a plan of action and know what they will do in advance if a report is made. Have the organisers discussed how they will react if it's a close friend that's caused the problem? Have they talked about what actions will result in a warning & which will require the attendee to leave the conference? Most importantly is it clear how any such decisions will be made and who will actually talk to the harassing attendee? I really hope so as it's incredibly hard to sort these things out during an actual incident.

I think the visibility of any code of conduct says a lot about how important inclusiveness is to the conference organisers. A conference I attended recently called out the code of conduct right at the start of the introduction. I liked this a lot.

Inclusiveness is important to me. Going forwards, this will be a factor I take into account when deciding to attend a conference. The conference organisers set the tone they expect and then it is for us, the attendees, to call out any unacceptable behaviour we see before it becomes an incident needing to be reported.

Using ZF2 Forms with Twig

Following on from looking at how to integrate Zend Framework 2 forms into Slim Framework, let's look at the changes required if you also happen to want to use Twig.

When it comes to rendering the form, we would want our template to look like this:

    <form method="POST" role="form">
        <div class="form-group">
            {{ formRow(form.get('email')) }}
        </div>
        {{ formElement(form.get('submit')) }}
    </form>

The ZF2 view helpers, formRow and formElement now look like Twig functions, however we don't want to have to rewrite all our ZF2 view helpers into Twig. Fortunately, Twig supports the concept of a undefined function callback is called whenever Twig encounters a function that it doesn't know how to call. We can use this to proxy through to the Zend\View system and get it to render the ZF2 view helpers.

Let's look at how we do this.

Starting with the work in the previous article, we merely need to change the view layer. That is, we don't need a custom PHP view (\RKA\View), but instead use the Slim-Views Twig component.

To add Twig to a Slim project we update composer.json and add "twig/twig": "1.16.*" and "slim/views": "0.1.*" to the require section and then run composer update.

We then update index.php to add the view class when instantiating the Slim and configure it.:

$app = new \Slim\Slim([
    'view' => new \Slim\Views\Twig()
]);

// Configure Twig
$view = $app->view();
$view->parserOptions = [
    'debug' => true,
    'cache' => false,
];
$view->parserExtensions = array(
    new \Slim\Views\TwigExtension(),
);

Now, whenever we call $app->render(), Twig will be used. The view templates have a .twig extension, so we rename home.php to home.twig and it's rendered like this:

$app->render('home.twig', [
    'form' => $form
]);

To integrate the ZF2 Form view helpers without having to rewrite them all as Twig extensions, we just need to register an undefined callback function that uses Zend Framework 2's PhpRenderer to render the view helper.

This is done with a few lines in index.php:

$viewHelperManager = $app->serviceManager->get('ViewHelperManager');
$renderer = new \Zend\View\Renderer\PhpRenderer();
$renderer->setHelperPluginManager($viewHelperManager);

$view->getInstance()->registerUndefinedFunctionCallback(
    function ($name) use ($viewHelperManager, $renderer) {
        if (!$viewHelperManager->has($name)) {
            return false;
        }

        $callable = [$renderer->plugin($name), '__invoke'];
        $options  = ['is_safe' => ['html']];
        return new \Twig_SimpleFunction(null, $callable, $options);
    }
);

Let's break this down.

Firstly, we create a PhpRenderer and set the helper plugin manager to ViewHelperManager that's already defined in the service manager.

We can access the underlying Twig_Environment object using $view->getInstance() and then call registerUndefinedFunctionCallback with a closure for our callback.

The callback code is this bit:

    function ($name) use ($viewHelperManager, $renderer) {
        if (!$viewHelperManager->has($name)) {
            return false;
        }

        $callable = [$renderer->plugin($name), '__invoke'];
        $options  = ['is_safe' => ['html']];
        return new \Twig_SimpleFunction(null, $callable, $options);
    }

Within the callback we check the ViewHelperManager to see if we have a view helper that we can call. If we don't then we return false so that Twig can try a difference undefined function callback in its list.

If we do know about this view helper, then we instantiate a Twig_SimpleFunction that will call the __invoke method of the view helper which we retrieve via the plugin method of the renderer. Note that we also tell Twig that our view helper is safe to use with HTML so it doesn't escape the HTML tags created by the view helper.

That's all there is to it.

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

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.

Throw an exception when simplexml_load_string fails

I keep having to look up how to stop the warning that are emitted when simplexml_load_string & simplexml_load_file fail, so this time I've written the world's simplest little class to take care of it for me from now on:

<?php

namespace Rka;

use UnexpectedValueException;

class Xml
{
    /**
     * Load an XML String and convert any warnings to an exception
     *
     * @param string  $string
     * @param string $class_name
     * @param int $options
     * @param string $ns
     * @param bool $is_prefix
     *
     * @throws UnexpectedValueException
     *
     * @return SimpleXMLElement
     */
    public static function loadXMLString(
        $string,
        $class_name = "SimpleXMLElement",
        $options = 0,
        $ns = "",
        $is_prefix = false
    ) {
        $previous = libxml_use_internal_errors(true);

        $xml = simplexml_load_string($string, $class_name, $options, $ns,
                 $is_prefix);

        if (!$xml) {
            $errors = self::getXMLErrorString();
            libxml_use_internal_errors($previous);
            throw new UnexpectedValueException($errors);
        }

        libxml_use_internal_errors($previous);
        return $xml;
    }

    /**
     * Load an XML File and convert any warnings to an exception
     *
     * @param string  $string
     * @param string $class_name
     * @param int $options
     * @param string $ns
     * @param bool $is_prefix
     *
     * @throws UnexpectedValueException
     *
     * @return SimpleXMLElement
     */
    public static function loadXMLFile(
        $string,
        $class_name = "SimpleXMLElement",
        $options = 0,
        $ns = "",
        $is_prefix = false
    ) {
        $previous = libxml_use_internal_errors(true);

        $xml = simplexml_load_file($string, $class_name, $options, $ns,
                 $is_prefix);

        if (!$xml) {
            $errors = self::getXMLErrorString();
            libxml_use_internal_errors($previous);
            throw new UnexpectedValueException($errors);
        }

        libxml_use_internal_errors($previous);
        return $xml;
    }

    /**
     * Helper method to format the XML errors into a string.
     *
     * @return string
     */
    protected static function getXMLErrorString()
    {
        $message = '';
        foreach (libxml_get_errors() as $error) {
            $message .= trim($error->message)
              . " on line: $error->line, column: $error->column.\n";
        }
        libxml_clear_errors();
        return trim($message);
    }
}

Update: The code has been updated based on the comments below. Thanks!

Redirecting in Slim Middleware

I recently ran into a problem with calling redirect() in a Slim framework middleware class, so I thought I'd better document the solution so that I remember in future!

The authentication middleware looks roughly like this:

class Authentication extends \Slim\Middleware
{
    public function call()
    {
        if (!$this->isLoggedIn()) {
            $app = \Slim\Slim::getInstance();
            $app->redirect($app->urlFor('login'));
        }

        $this->next->call();
    }

    // other methods...
}

The problem is that you can't call $app->redirect() in Middleware as redirect() is designed to only run in application route callbacks and Middleware should only act on the request and response objects.

This is because the app's redirect() method calls through to stop() which throws a Stop exception which is intended to be caught by the app's call() method. However, if this exception is thrown in middleware, then it isn't caught by call() and the redirect doesn't work. What's weird is that it works on some (most?) web servers but not all.

The correct way to redirect in middleware, use the Response's redirect() method instead like this:

class Authentication extends \Slim\Middleware
{
    public function call()
    {
        if (!$this->isLoggedIn()) {
            $app = \Slim\Slim::getInstance();
            return $app->response()->redirect($app->urlFor('login'));
        }

        $this->next->call();
    }

    // other methods...
}

Note that we return the result of $response->redirect() so that the next item in the chain isn't called.

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);