Dependency injection in Slim framework

Slim framework comes with a Dependency Injection container called Set.

The basics

The DIC is accessed via the container property of $app.

To set, you use the set() method:

$app->container->set('foobar', function() { return new Foo\Bar(); } );

If you need a given resource to be shared, then use the singleton method:

$app->container->singleton('foobar', function() { return new Foo\Bar(); } );

And then to retrieve from the container, there are multiple ways to do it:

$fooBar = $app->container->get('foobar');
$fooBar = $app->container['foobar'];
$fooBar = $app->foobar;

The shortcut version ($app->foobar) only works if the key is a valid PHP variable name.

Dependent dependencies

The callback is passed the container instance, you can also retrieve dependencies and use them to configure the object that you are instantiating as shown in this example:

$app = new \Slim\Slim(
    [
        'db.dsn' => 'mysql:host=localhost;dbname=something',
        'db.username' => 'user',
        'db.password' => 'pass',
    ]
);

// Add dependencies
$app->container->singleton('PDO', function ($container) {
    $settings = $container['settings'];
    return new PDO($settings['db.dsn'], $settings['db.username'], $settings['db.password']);
});

$app->container->singleton('UserMapper', function ($container) {
    $pdo = $container['PDO'];
    return new User\UserMapper($pdo);
});

// Routes
$app->get('/', function ($name) use ($app) {
    // (Using the DIC as a service locator here...)
    $userMapper = $app->container['UserMapper'];
    // etc...
});

In this example the '/' route uses an instance of User\Mapper, which requires a PDO object. We therefore add two resources to the container: one for PDO and one for the UserMapper.

Slim uses its own DIC

Note that Slim uses it's own DIC for all its dependencies which means that you can override them. It even uses it for the configuration settings that you pass in to the Slim constructor which is accessible via $app->container['settings'] (or $app->settings).

The classic example is that Slim creates its own instance of Slim\Log and attaches it to the log key in the container. If you wanted to use monolog instead, you can simply do:

$app->container->singleton('log', function () {
    $log = new \Monolog\Logger();
    $log->pushHandler(new \Monolog\Handler\StreamHandler('path/to/log.txt'));
    return $log;
});

We have now replaced Slim's own log system with monolog.

Fin

I know that it's tempting to grab $app directly, but Slim makes is nearly as easy to configure your objects using dependency injection which will make your life much easier in the long-run!

Setup Doctrine Migrations to update MySQL timestamp on update

One project I'm working on uses MySQL exclusively and is also using Doctrine Migrations.

I wanted to set up a column called updated that was automatically set to the timestamp of the last time the row was changed.

This is done in SQL like this:

CREATE TABLE foo (
    id INT AUTO_INCREMENT NOT NULL,
    bar VARCHAR(100) NOT NULL,
    updated timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    PRIMARY KEY(id)
);

It's not quite obvious how to do this in Migrations as it is designed to be portable across database engines and clearly this is a MySQL-ism.

To do it, you use the columnDefinition option to addColumn() within your up() method, like this:

public function up(Schema $schema)
{
  $myTable = $schema->createTable('foo');
  $myTable->addColumn('id', 'integer', ['autoincrement'=>true]);
  $myTable->addColumn('bar', 'string', ['length' => 100]);
  $myTable->addColumn(
    'updated',
    'datetime',
    ['columnDefinition' => 'timestamp default current_timestamp on update current_timestamp']
  );

  $myTable->setPrimaryKey(['id']);
}

The columnDefinition option replaces the type (which you must still set it to a valid portable option) and so you can easily put in database-specific definitions when you need to.

Shorter directory text in Bash prompt

Rather helpfully, David Goodwin left a comment about how he shortens the space taken up by the directory section of his terminal's PS1 prompt by using a Bash script to remove the middle portion.

This is a really good idea, so I ported it into my PS1 set up which resulted in some rearranging and thought I'd share here as I modified for OS X and I don't want to lose it!

The relevant portion of my .profile is:

# Git information for prompt
if [ -f $(brew --prefix)/etc/bash_completion.d/git-prompt.sh ]; then
    . $(brew --prefix)/etc/bash_completion.d/git-prompt.sh
fi
GIT_PS1_SHOWDIRTYSTATE=true
GIT_PS1_SHOWUNTRACKEDFILES=true

# Shorten current directory - Based on function by David Goodwin
function shorten_pwd()
{
    LENGTH="40"
    PART1="10"
    PART2="27"

    DIR=`echo "${PWD}" | sed "s/\\/home\\/$USER/~/" | sed "s/\\/Users\\/$USER/~/"`

    if [ ${#DIR} -gt $(($LENGTH)) ]; then
        echo "${DIR:0:$(($PART1))}...${DIR:$((${#DIR}-$PART2)):$PART2}"
    else
        echo "$DIR"
    fi
}


# Set prompt
prompt_cmd () {
    LAST_STATUS=$?

    local COLOUR_RESET='\[\e[0m\]'
    local BLACK='\[\e[0;30m\]'
    local RED='\[\e[0;31m\]'
    local GREEN='\[\e[0;32m\]'
    local YELLOW='\[\e[0;33m\]'
    local BLUE='\[\e[0;34m\]'
    local PURPLE='\[\e[0;35m\]'
    local CYAN='\[\e[0;36m\]'
    local WHITE='\[\e[0;37m\]'
    local BOLD_BLACK='\[\e[1;30m\]'
    local BOLD_RED='\[\e[1;31m\]'
    local BOLD_GREEN='\[\e[1;32m\]'
    local BOLD_YELLOW='\[\e[1;33m\]'
    local BOLD_BLUE='\[\e[1;34m\]'
    local BOLD_PURPLE='\[\e[1;35m\]'
    local BOLD_CYAN='\[\e[1;36m\]'
    local BOLD_WHITE='\[\e[1;37m\]'

    PS1="$BLACK\u@\h"     # user@host
    PS1+=" "
    PS1+="$BLUE"
    PS1+=$(shorten_pwd)   # current directory (usually \w)
    PS1+=" "
    PS1+="$RED"
    PS1+=$(__git_ps1)     # git status
    PS1+="$COLOUR_RESET"
    PS1+='\$ '
}

PROMPT_COMMAND='prompt_cmd && tab_title'

There are three sections here. Firstly we ensure that git-prompt.sh is loaded and configure a couple of settings for it. Then we write a function called shorten_cwd() based on David's script. The main changes here are that I also look for /Users/$USER as that's where OS X stores home directories and that I don't split in the middle. Finally we define prompt_cmd() to set PS1 in a way that I understand and assign it to PROMPT_COMMAND along with tab_title.

The end result looks like this:

Shorter prompt

Setting OS X's Terminal Tab to the current directory

I use many tabs in a Terminal window quite frequently, and while the window title will show the current directory name, the tab title doesn't. You can manually change it using shift+cmd+i, but who can be bothered?

Automating it so that the tab title always matches the current directory turns out to be really easy and just requires a few lines in ~/.profile.

Firstly, we need a function that sets the tab's title to the last segment of the current working directory:

function tab_title {
  echo -n -e "\033]0;${PWD##*/}\007"
}

We then just need to automate calling it whenever we change directory. The easiest way to do this is to change PROMPT_COMMAND:

PROMPT_COMMAND="tab_title ; $PROMPT_COMMAND"

That's it. Whenever I change directory, or open a new terminal tab, then the tab's title now matches the last segment of the directory for that tab. Much more useful!

Terminal tabs

Exclude elements from Zend\Form's getData()

If you need to exclude some elements from a Zend\Form's getData(), the easiest way is to use a validation group.

For example:

class SomeForm extends \Zend\Form\Form implements
    \Zend\InputFilter\InputFilterProviderInterface
{
    public function init()
    {
        $this->add([
            'name' => 'name',
            'options' => ['label' => 'Name'],
        ]);
        
        $this->add([
            'name' => 'email',
            'options' => ['label' => 'Email'],
        ]);

        $this->add([
            'name' => 'submit',
            'type' => 'button',
            'options' => [
                'label' => 'Filter',
            ],
        ]);

        $this->setValidationGroup(['name', 'email']);
    }

    public function getInputFilterSpecification()
    {
        // return input filter specification here
    }
}

The call to setValidationGroup() contains an array of all the elements you want to be validated. In this case we list all elements except the submit button. The side-effect is that when you called $form->getData(), only the values for those elements are returned which can sometimes be useful.

Generalise

Rather than having to re-list all the elements again, it's more usually to only want to exclude one or two elements. The easiest way to do this is to extend Zend\Form\Form and override isValid() to set up a validation group based on an option called 'exclude' that you apply to the relevant elements:

<?php
namespace My;

class Form extends \Zend\Form\Form
{
    /**
     * Override isValid() to set an validation group of all elements that do not
     * have an 'exclude' option, if at least one element has this option set.
     *
     * @return boolean
     */
    public function isValid()
    {
        if ($this->hasValidated) {
            return $this->isValid;
        }

        if ($this->getValidationGroup() === null) {
            // Add all non-excluded elements to the validation group
            $validationGroup = null;
            foreach ($this->getElements() as $element) {
                if ($element->getOption('exclude') !== true) {
                    $validationGroup[] = $element->getName();
                }
            }
            if ($validationGroup) {
                $this->setValidationGroup($validationGroup);
            }
        }
        
        return parent::isValid();
    }
}
?>

Then, to exclude an element, we extend SomeForm from \My\Form and just add a new 'exclude' option to the elements we wish to exclude. For example:

// in SomeForm::init()
$this->add([
    'name' => 'submit',
    'type' => 'button',
    'options' => [
        'label' => 'Filter',
        'exclude' => true,
    ],
]);

and this element will be automatically excluded when you call $form->getData().

2014 in pictures

2014 is coming to an end, so as usual, let's look back at the year as highlighted by the photos that I've taken. This year, I took at least one photo every day, so there's been plenty for me to choose from!

January

As usual, there was flooding in Worcester, but clearly the biggest personal event of the month was breaking my elbow while skateboarding with my son at the end of the month. This clearly impacted my business for February and March as I could barely type for a good six or so weeks.

Still flooded Elbow in a cast

February

My wife and I celebrated her birthday in Barcelona and we discovered Licor 43!

Coffee on La Rambla A glass of Licor 43

March

A quiet month where my cast was removed. I also attended the PHPNE 14 conference in Newcastle.

Removing the cast Staff in their hoodies

April

In April, we went to the Insomnia Gaming Festival where the kids got to test out new games. We also sold our caravan.

At Insomnia 51 2003 Avondale Dart 556-6

May

May is a month of birthdays as both kids and myself celebrate.

New scooter Birthday boy playing on his new 3DS XL

June

The highlights of June were watching Great Britain play Belgium at basketball (and win!) and attending DPC.

Collins "We are screwed"

July

I was very pleased to speak at OSCON in Portland! It's an amazing conference and I would very much like to go to the European edition in Amsterdam next year.

Portland Saturday Market Discussing Atom

August

In August, I went on holiday with my family and, at the end of the month, we celebrated my cousin's marriage.

Playing in the pool Lucy & Dave

September

I attended and presented at the inaugural Endpoint conference where I learned much from Ben Longden. I also attended the Hackference conference day which was filled with interesting content.

Ben Erika Heidi

October

By October, I was heavily into conference season and presented at both PHPNW and ZendCon. Sandwiched between them, I also attended the All Your Base conference. This month, we also bought our own squat rack and cancelled our gym membership.

Khayrattee (aka 7PHP) Completed squat rack! Did you *really* write those tests?

November

I was privileged to be be invited to speak at PHP Argentina! An very well organised conference with a number of well known community people that I was delighted to meet and spend time with. How can you not love a conference where there are deck chairs in the hallway track? I also attended PHPEM's unconference; It turns out that I have opinions on how to give a good talk too! This was a good event and I hope they hold it again next year.

Phil, preparing for his talk The final schedule

December

My eldest son finished building his remote control car kit this month. This was a great project and very enjoyable. At the close of the year, I bought a new camera, so I expect that I'll be taking lots of photos next year too!

The Hornet It's not as big as an Elephpant!

I'm looking forward to 2015!

Recursively deleting elements from an array

I had a need recently to delete items from a nested associative array and also any empty sub-arrays. My initial thought was to use array_walk_recursive, but this doesn't work as you can't unset nested elements and you only have access to the leaves. Clearly I needed a recursive function.

I'm sure that this has been done many times before, but this is my solution:

/**
 * Remove any elements where the callback returns true
 *
 * @param  array    $array    the array to walk
 * @param  callable $callback callback takes ($value, $key, $userdata)
 * @param  mixed    $userdata additional data passed to the callback.
 * @return array
 */
function array_walk_recursive_delete(array &$array, callable $callback, $userdata = null)
{
    foreach ($array as $key => &$value) {
        if (is_array($value)) {
            $value = array_walk_recursive_delete($value, $callback, $userdata);
        }
        if ($callback($value, $key, $userdata)) {
            unset($array[$key]);
        }
    }

    return $array;
}

with this test:

class FunctionsTest extends \PHPUnit_Framework_TestCase
{
    public function testArrayWalkRecursiveDelete()
    {
        $array = [
            'a'=> 'a',
            'b'=> null,
            'c' => [
                'a' => null,
                'b' => 'b',
            ],
            'd' => [
                'a' => null
            ]
        ];

        $result = array_walk_recursive_delete($array, function ($value, $key) {
            if (is_array($value)) {
                return empty($value);
            }
            return ($value === null);
        });

        $expected = [
            'a'=> 'a',
            'c' => [
                'b' => 'b',
            ],
        ];

        $this->assertSame($expected, $result);
    }
}

This is very similar to how array_walk_recursive works except that I return the altered array rather than a boolean as it's a recursive function.

The test shows how I use it:

        $result = array_walk_recursive_delete($array, function ($value, $key) {
            if (is_array($value)) {
                return empty($value);
            }
            return ($value === null);
        });

If the callback returns true, then the element is deleted from the array, so for my case, I return true if the value is an empty array or null.

SSL certificate verification on PHP 5.6

I recently updated my local OS X Zend Server installation to PHP 5.6 and when I ran composer self-update, I got this error message:

[Composer\Downloader\TransportException]                                                                                       
The "https://getcomposer.org/version" file could not be downloaded: SSL operation failed with code 1. OpenSSL Error messages:  
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed                                              
Failed to enable crypto                                                                                                        
failed to open stream: operation failed 

Googling around, I finally worked out that there have been various SSL improvements in PHP 5.6 and that the problem was that it couldn't find any OpenSSL certificates on my system. This isn't a total surprise as OS X has been moving away from using OpenSSL internally in favour of its own libraries.

There's a new PHP function openssl_get_cert_locations that helps with this and so I ran:

$ php -r "print_r(openssl_get_cert_locations());"

on the command line to find out where PHP was looking. On my system, I got this:

Array
(
    [default_cert_file] => /usr/local/openssl-0.9.8zb/ssl/cert.pem
    [default_cert_file_env] => SSL_CERT_FILE
    [default_cert_dir] => /usr/local/openssl-0.9.8zb/ssl/certs
    [default_cert_dir_env] => SSL_CERT_DIR
    [default_private_dir] => /usr/local/openssl-0.9.8zb/ssl/private
    [default_default_cert_area] => /usr/local/openssl-0.9.8zb/ssl
    [ini_cafile] => 
    [ini_capath] => 
)

There is no directory /usr/local/openssl-0.9.8zb on my system and SSL_CERT_FILE and SSL_CERT_DIR are not defined, so it's no surprise that PHP was struggling.

To fix it, I install openssl via homebrew:

brew install openssl

This installs the openssl certificates to /usr/local/etc/openssl/cert.pem, so we can now use the new PHP 5.6 INI setting openssl.cafile to tell PHP where to find the certificates:

Adding

openssl.cafile=/usr/local/etc/openssl/cert.pem

to Zend Server's php.ini solved the problem and I can now use composer once again!

Overriding the built-in Twig date filter

In one project that I'm working on, I'm using Twig and needed to format a date received from an API. The date string received is of the style "YYYYMMDD", however date produced an unexpected output.

Consider this:

{{ "20141216"|date('jS F Y') }}

creates the output:

20th May 1976

This surprised me. Then I thought about it some more and realised that the date filter is treating my date string as a unix timestamp. I investigated and discovered the problem in twig_date_converter:

    $asString = (string) $date;
    if (ctype_digit($asString) 
        || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
        $date = '@'.$date;
    }

    $date = new DateTime($date, $defaultTimezone);

This code tests to see if the dates string provided is a number (positive or negative) and then prepends an '@' symbol to the front. This has the effect of informing DateTime's constructor to treat $date as a unix timestamp.

Unfortunately, twig_date_converter is a function and so I couldn't override it, so I wrote my own extension that registers new date, date_modify filters and a new date function in order to solve my problem:

<?php
/*
 * Extension to provide updated date & date_modify filters along with an
 * updated date function which do not auto-convert strings of numbers to
 * a unix timestamp.
 *
 * Code within dateFilter(), modifyFilter() and dateFromString() extracted
 * from Twig/lib/Twig/Extension/Core.php which is (c) 2009 Fabien Potencier
 * and licensed as per https://github.com/twigphp/Twig/blob/master/LICENSE
 */
namespace My\Twig\Extension;

use Twig_Extension;
use Twig_SimpleFilter;
use Twig_SimpleFunction;
use DateTime;
use DateTimeInterface;
use DateTimeImmutable;

class DateExtension extends Twig_Extension
{
    public function getName()
    {
        return 'my_date';
    }

    public function getFilters()
    {
        return array(
            new Twig_SimpleFilter('date', [$this, 'dateFilter'],
                    ['needs_environment' => true]),
            new Twig_SimpleFilter('date_modify', [$this, 'modifyFilter'],
                    ['needs_environment' => true]),
        );
    }

    public function getFunctions()
    {
        return array(
            new Twig_SimpleFunction('date', [$this, 'dateFromString'],
                    ['needs_environment' => true]),
        );
    }

    public function dateFilter($env, $date, $format = null, $timezone = null)
    {
        if (null === $format) {
            $formats = $env->getExtension('core')->getDateFormat();
            $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
        }

        if ($date instanceof DateInterval) {
            return $date->format($format);
        }

        return $this->dateFromString($env, $date, $timezone)->format($format);
    }

    public function modifyFilter($env, $date, $format = null, $timezone = null)
    {
        $date = $this->dateFromString($env, $date, false);
        $date->modify($modifier);

        return $date;
    }

    public function dateFromString($env, $date, $timezone)
    {
        // determine the timezone
        if (!$timezone) {
            $defaultTimezone = $env->getExtension('core')->getTimezone();
        } elseif (!$timezone instanceof DateTimeZone) {
            $defaultTimezone = new DateTimeZone($timezone);
        } else {
            $defaultTimezone = $timezone;
        }

        // immutable dates
        if ($date instanceof DateTimeImmutable) {
            return false !== $timezone ? $date->setTimezone($defaultTimezone) : $date;
        }

        if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
            $date = clone $date;
            if (false !== $timezone) {
                $date->setTimezone($defaultTimezone);
            }

            return $date;
        }

        $date = new DateTime($date, $defaultTimezone);
        if (false !== $timezone) {
            $date->setTimezone($defaultTimezone);
        }

        return $date;
    }
}

This class simply registers new date, date_modify filters and a new date function to replace the ones in Twig core and then is a direct copy of the functions twig_date_format_filter, twig_date_modify_filter and twig_date_converter with the functionality above removed.

I also needed to register this extension with the Twig_Environment using: $env->addExtension(new \My\Twig\Extension\DateExtension()); and I'm done.

{{ "20141216"|date('jS F Y') }}

now correctly outputs:

16th December 2014

While, it's a shame I can't just override twig_date_converter, I'm glad that I can re-register the relevant Twig filters and function.

Converting databases between MySQL and SQL Server

I regularly deal projects that target SQL Server, but mostly develop against MySQL to avoid having to run a full Windows stack locally all the time. One of the nice things about PHP with DBAL and Migrations is that the database is pretty well abstracted from my code. Of course, this means that I don't target any of the specialist features, but for these projects, this hasn't been an issue.

To convert data from SQL Server to MySQL, I've found Intelligent Converters's MSSQL to MySQL tool to work very well for my needs. It allows me to grab the test database from the client and convert it to a MySQL dump file that I can then import.