Do you need Zend Framework training or consultancy? Get in touch!

Using Eloquent in Slim Framework

Eloquent is one of the easier ORMs to get started with, so let's look at how to use and test it within a Slim Framework application.

Set up

You can add Eloquent to your Slim project via composer:

composer require illuminate/database

You now need to bootstrap it. To do this we need some settings, so lets do that in our standard Slim configuration:

$config = [
    'settings' => [
        'db' => [
            // Eloquent configuration
            'driver'    => 'mysql',
            'host'      => 'localhost',
            'database'  => 'bookshelf',
            'username'  => 'rob',
            'password'  => '123456',
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
        ],
    ],
]

We then instantiate the capsule manager and boot Eloquent , probably in index.php:

$container = $app->getContainer()
$capsule = new Illuminate\Database\Capsule\Manager;
$capsule->addConnection($container->get('settings')['db']);
$capsule->bootEloquent();

From this point, you can simply follow the documentation to create your Eloquent models and use them within your route callables.

Testing your models

At first glance, it seems quite hard to test your models as the find method is a static.

For example, consider this route callback within an action class:

public function __invoke($request, $response, $args)
{
    return $this->view->render($response, 'author/list.twig', [
        'authors' => Author::all(),
    ]);
});

To test it, I followed a tip from Taylor Otwell and wrapped the find method in a class that I could then mock:

namespace Bookshelf;

class AuthorRepository
{
    public function all()
    {
        return Author::all();
    }

    public function find($id, $columns = ['*'])
    {
        return Author::find($id, $columns);
    }
}

The callback then becomes:

public function __invoke($request, $response, $args)
    return $this->view->render($response, 'author/list.twig', [
        'books' => $this->authorRepository->all()
    ]);
});

Like the view, the authorRepository is injected into the constructor and so can be mocked out. The full class looks like this:

namespace Bookshelf;

use Slim\Views\Twig;
use Bookshelf\AuthorRepository;

final class ListAuthorsAction
{
    private $view;
    private $authorRepository;

    public function __construct(
        Twig $view,
        AuthorRepository $authorRepository
    ) {
        $this->view = $view;
        $this->authorRepository = $authorRepository;
    }

    public function __invoke($request, $response, $args)
    {
        return $this->view->render($response, 'author/list.twig', [
            'authors' => $this->authorRepository->all()
        ]);
    }
}

To test using PHPUnit, we start by creating a mock for the AuthorRepository:

$authorRepository = $this->getMockBuilder(AuthorRepository::class)
    ->setMethods(['all'])
    ->disableOriginalConstructor()
    ->getMock();

Then we set the expectation that the all method will return a collection of Authors:

use Illuminate\Database\Eloquent\Collection;
$authors = new Collection([
    new Author(['name' => 'test1']),
    new Author(['name' => 'test2']),
    new Author(['name' => 'test3']),
]);

$authorRepository->expects($this->once())
    ->method('all')
    ->willReturn($authors);

We do the same for the view object:

$view = $this->getMockBuilder(Twig::class)
    ->setMethods(['render'])
    ->disableOriginalConstructor()
    ->getMock();

$response = new Response();
$response->write('Rendered text on page');

$this->view->expects($this->once())
    ->method('render')
    ->with($response, 'author/list.twig', ['authors' => $authors])
    ->willReturn($response);

Now we can instantiate the action class with our two mock objects. We then create a request and response objects and run the action and test the returned response:

$action = new ListAuthorsAction($view, $authorRepository);
$request = Request::createFromEnvironment(Environment::mock());
$response = new Response();

$ReturnedResponse = $action($request, $response);

$this->assertSame((string)$ReturnedResponse->getBody(), 'Rendered text on page');

Summary

Eloquent is quite a nice implementation of the Active Record pattern. If that meets your needs, then it is easy to use within a Slim Framework application, so go for it!

Free the Geek appearance

I was delighted to spend an hour chatting with Matt Setter on episode 14 of Free the Geek. In this podcast, we talked about Slim 3 and development in general.

Matthew gives me a great introduction (thanks!) and then we delve into the chat.

We talk about the importance of semantic versioning as I think this is key to stability in a project. It's not very glamorous to work on code where you have to maintain backwards compatibility, but it is vital to the success of a library such as Slim Framework. Developers who use your library depend on it and semantic versioning is the way to ensure that no-one is surprised. I hate it when a library I use has BC break between patch or minor version numbers that results in my software breaking, so I try not to break the contract in Slim. Of course, sometimes we've introduced a BC break by accident, but when we have, we've fixed it immediately.

We also spoke about software architecture and how important it is to make the right decisions for your project and the team building it. Projects have a lifecycle and it's useful to build appropriately for the stage that your project is at. I'm a fan of architecting for the near term with an eye to the likely long term paths and ensuring that your design fits the project.

We talk about this and more, so have a listen.

A few composer tips

I recently learned about a couple of features of composer that I thought I'd write down here so that I don't forget them! I also had to deal with a conflict in composer.lock recently, so I've noted down how I solved that too.

List installed versions

To list the current versions of all installed dependencies:

composer show -i 

The output looks something like:

container-interop/container-interop 1.1.0  Promoting the interoperability of container objects...
monolog/monolog                     1.17.2 Sends your logs to files, sockets, inboxes, ...
nikic/fast-route                    v0.6.0 Fast request router for PHP
pimple/pimple                       v3.0.2 Pimple, a simple Dependency Injection Container
psr/http-message                    1.0    Common interface for HTTP messages
psr/log                             1.0.0  Common interface for logging libraries
slim/php-view                       2.0.6  Render PHP view scripts into a PSR-7 Response...
slim/slim                           3.1.0  Slim is a PHP micro framework that helps you...

Very useful for working out exactly what's installed.

Set PHP version

To set the version of PHP that composer will use to resolve dependencies, add this to your composer.json file:

    "config": {
        "platform": {
            "php": "5.6.19"
        }
    },

You can now run composer update on a PHP 7 installation and it will create a composer.lock file suitable for a server running PHP 5.6.19.

Resolving a conflict in composer.lock

When you merge a feature branch into develop and get a conflict in composer.lock, I've found these strategies work best for me:

Just the hash

If the only conflict is in the "hash" and "content-hash" lines, then pick either choice and then run:

composer update --lock

Any other conflict

For any other conflict where you want to keep the current set of versions on develop:

  1. Retrieve the correct lock file for develop: git merge --ours
  2. Add in each new dependency in the merged composer.json that's not in the original develop's composer.json using
    composer require {vendor/package}

The end result is a composer.lock file with the original information from develop along with the new packages from the feature branch.

Configuration in Slim Framework

Configuration in Slim Framework is nice and simple: the App's constructor takes a configuration array for the DI container;

$config = [];
$app = new Slim\App($config);

Setting up

The settings sub-array is used to hold the settings of your application:

$config = [
    'settings' => [
        'displayErrorDetails' => true,

        'logger' => [
            'name' => 'slim-app',
            'level' => Monolog\Logger::DEBUG,
            'path' => __DIR__ . '/../logs/app.log',
        ],
    ]
];
$app = new Slim\App($config);

Slim comes with a number of settings that you can change. The most important is displayErrorDetails. This defaults to false, but if you set it to true, then it will display the details of any exceptions when rendering an error page. Ensure that this is set to false in production!

You can put any other settings you want to in the configuration, under any name you like as I have done with the logger key, which contains information about how to configure a Monolog instance.

Retrieving settings

The settings are stored in the DI container so you can access them via the settings key in container factories. For example, I can set up a container factory for to create my Monolog instance like this:

$container = $app->getContainer();
$container['logger'] = function ($c) {
    $settings = $c->get('settings')['logger'];
    $logger = new Monolog\Logger($settings['name']);
    $logger->pushProcessor(new Monolog\Processor\UidProcessor());
    $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
    return $logger;
};

Slim's default container is Pimple, so we use the array notation to register a new service called 'logger' in this case. The logger settings are retrieved using $settings = $c->get('settings')['logger'].

Separate file for configuration

You should use a separate file for your configuration. This is most easily done using require. Firstly, we create config.php and return the configuration array from it:

config.php:

<?php

return [
    'settings' => [
        'displayErrorDetails' => true,

        'logger' => [
            'name' => 'slim-app',
            'level' => Monolog\Logger::DEBUG,
            'path' => __DIR__ . '/../logs/app.log',
        ],
    ]
];

As require returns whatever the included file returns, we load like this:

$config = require 'config.php';
$app = new \Slim\App($config);

Environment variables using dotenv

Consider using environment variables for the differences between servers. These can be configured as part of your apache/nginx setup or you can use dotenv.

In this case, create a .env file and add it to .gitignore:

.env:

DISPLAY_ERRORS=0
LOG_LEVEL=400

We can use these environment variables in our configuration file via getenv:

config.php:

<?php

return [
    'settings' => [
        'displayErrorDetails' => (bool)getenv('DISPLAY_ERRORS'),

        'logger' => [
            'name' => 'slim-app',
            'level' => (int)getenv('LOG_LEVEL') ?: 400,
            'path' => __DIR__ . '/../logs/app.log',
        ],
    ]
];

We then load using:

$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();
$config = require 'config.php';
$app = new \Slim\App($config);

Multiple configuration files

You may also want to split your configuration into multiple files which are then merged together with files loaded later overriding the settings from previous files.

One use case is to have a local.config.php that's not in git that contains per-server configuration to be merged with your master configuration in config.php.

For example, your live configuration should turn off display of errors and maybe set a different logging level:

local.config.php:

<?php
return [
    'settings' => [
        'displayErrorDetails' => false,

        'logger' => [
            'level' => Monolog\Logger::ERROR,
        ],
    ]
];

Merging the two arrays is a little complicated as array_merge_recursive doesn't do what you expect and will result in the 'displayErrorDetails' key becoming an array with two elements. You could write your own merge method, but it's easier to use zend-stdlib's merge method:

$config = require 'config.php';
$localConfig = require 'local.config.php';
$config = Zend\Stdlib\ArrayUtils::merge($config, $localConfig);
$app = new \Slim\App($config);

Ini/Yaml/JSON/XML configuration

If you want to use something other than PHP arrays, then use zend-config along with glob. In this case, you place all your configuration files in a single directory, such as config/ and then name them with .global.{type} and .local.{type} to control order.

For example, to load the configuration files global.yaml, db.global.yaml and then local.yaml in that order:

$config = Zend\Config\Factory::fromFiles(glob('config/{global, *.global, local}.*', GLOB_BRACE));

One useful feature of zend-config is that you can mix and match between formats, so local.yaml could be local.ini and it would still work.

Summary

As you can see, using configuration with Slim is very easy; all the choices come down to how you want to organise your configuration so that you can manage the differences between environments easily.

Testing Slim Framework actions

To test a Slim Framework action, you need a request and a response object and mock whatever is in the action. This is one way to do this.

Consider this simple echo action that returns the query parameters to you as a JSON encoded string:

$ curl "http://localhost:8888/echo?foo=bar&this=that"
{"foo":"bar","this":"that"}

This is one of those useful API endpoints that your users can use to check that everything is working as expected.

The action under test

The code for this endpoint is a class called EchoAction which looks like this:

namespace App\Action;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;

class EchoAction
{
    public function __invoke(Request $request, Response $response, $args = [])
    {
        return $response->withJson($request->getQueryParams());
    }
}

and it is registered with the Slim App like so:

$app->get('/echo', App\Action\EchoAction::class);

Testing

Testing it isn't too complex as there are no dependencies on the EchoAction class itself, so we just have instantiate the class, invoke it and write an test.

For a URL of the form /echo?foo=bar, the core test code is:

$action = new \App\Action\EchoAction();
$response = $action($request, $response);
$this->assertSame((string)$response->getBody(), json_encode(['foo' => 'bar']));

Creating the request and response

Creating the $response is easy as the constructor parameters all have defaults:

$response = new \Slim\Http\Response();

The $request is a little more complex as it's constructor signature looks like this:

public function __construct(
        $method,
        UriInterface $uri,
        HeadersInterface $headers,
        array $cookies,
        array $serverParams,
        StreamInterface $body,
        array $uploadedFiles = []
    )

However, Slim actually creates a Slim\Http\Request using the static createFromEnvironment() factory method, which takes a Slim\Http\Environment instance. Roughly, it does this:

$request = Request::createFromEnvironment(new Environment($_SERVER));

Setting up a $_SERVER array with the relevant elements can be a little tiresome in testing though. Fortunately, Josh needed to test Slim itself, so the Environment object has a handy static method called mock() that does this for us.

We use it like this:

$environment = \Slim\Http\Environment::mock([
    'REQUEST_METHOD' => 'GET',
    'REQUEST_URI' => '/echo',
    'QUERY_STRING'=>'foo=bar'
]);

As you can see, mock() takes an array which contains $_SERVER keys that we wish to set up for our particular test. Usually we set the REQUEST_METHOD, REQUEST_URI and, if we need it, QUERY_STRING. We need QUERY_STRING as this is the key in $_SERVER that Request uses to determine the query parameters for the request.

Putting it all together

Hence, our completed test looks like this:

class EchoActionTest extends \PHPUnit_Framework_TestCase
{
    public function testGetRequestReturnsEcho()
    {
        // instantiate action
        $action = new \App\Action\EchoAction();

        // We need a request and response object to invoke the action
        $environment = \Slim\Http\Environment::mock([
            'REQUEST_METHOD' => 'GET',
            'REQUEST_URI' => '/echo',
            'QUERY_STRING'=>'foo=bar']
        );
        $request = \Slim\Http\Request::createFromEnvironment($environment);
        $response = new \Slim\Http\Response();

        // run the controller action and test it
        $response = $action($request, $response, []);
        $this->assertSame((string)$response->getBody(), '{"foo":"bar"}');
    }
}

All in all, it's not too complicated to test a Slim action and Environment::mock() makes it easy to set up particular test cases.

Determining the image type of a file

One thing I learnt recently which I probably should have known already is that getimagesize() returns more than just the width and height of the image.

I've always used it like this:

list($width, $height) = getimagesize($filename);

However, getimagesize() also returns up to 5 more pieces of information.

Interestingly, the data array is a mix of indexed elements and named elements For example, for a file I uploaded while testing a PR, the output of print_r(getimagesize($filename)) is:

Array
(
    [0] => 1440
    [1] => 1440
    [2] => 3
    [3] => width="1440" height="1440"
    [bits] => 8
    [mime] => image/png
)

A very strange decision when designing the response of this function!

Index 2 is the file type which is an "IMAGETYPE" constant, such as IMAGETYPE_PNG. Note that there's also a constant called IMG_PNG, but this is a different number, so make sure you use the right one!

This is useful for file uploads. Consider this data in $_FILES:

Array
(
    [image] => Array
        (
            [name] => myimage
            [type] => application/octet-stream
            [tmp_name] => /tmp/phpbGyIq7
            [error] => 0
            [size] => 90410
        )

)

In this case, there is no extension on the uploaded filename and the type is application/octet-stream. If I want to resize this image using gd, then I'm going to need to know whether to use imagejpeg, imagegif or imagepng. A simple way to do this is something like this:

list($width, $height, $imageType) = getimagesize($filename);

// create $smallImage using imagecreatetruecolor() and imagecopyresampled(), etc.

// save image
switch ($imageType) {
    case IMAGETYPE_JPEG:
        imagejpeg($smallImage, $event_image_path . $small_filename);
        break;
    case IMAGETYPE_GIF:
        imagegif($smallImage, $event_image_path . $small_filename);
        break;
    case IMAGETYPE_PNG:
        imagepng($smallImage, $event_image_path . $small_filename);
        break;
    default:
        throw new Exception("Unable to deal with image type");
}

It's easier than dealing with the type from $_FILES too and by writing it down, maybe I'll remember it.

Improved error handling in Slim 3.2.0

We released Slim 3.2.0 yesterday which includes a number of minor bug fixes since 3.1.0 and also a few nice improvements around the way we handle errors.

Writing to the error log

Slim has a simple exception handler implemented that displays an error page that looks like this:

Slim error 1

It's not very informative, is it? That's intentional as we don't want to leak information on a live website. To display the error information you need to enable the displayErrorDetails setting like this:

$config = [
    'settings' => [
        'displayErrorDetails' => 1,
    ],
];

$app = new \Slim\App($config);

Not too difficult, if you know the setting! If you don't then you're staring at a blank page and have no idea what to do next.

To help solve this Slim 3.2.0 now writes the error information to the PHP error log when the displayErrorDetails is disabled. The built in PHP web server writes the error log to stderr, so I see this in my terminal:

Slim error 2

As you can see, all the information needed to find the issue is there, so the developer can get on with her day and solve the problem at hand.

PHP 7 errors

One of the new features of PHP 7 is that it can throw Error exceptions which can then be caught and processed as you would with a standard Exception. However, Slim 3's error handler is type hinted on Exception and so doesn't catch these new Errors.

To resolve this, Slim 3.2 ships with a new PHP 7 error handler which works exactly like the current exception handler but it catch Error. Here's an example (with displayErrorDetails enabled!):

Slim error 3

To sum up

I'm very happy to have more robust error handling in Slim as I think good errors as key for usability and makes Slim that much easier and enjoyable to use. If you find any error situations in Slim that you feel could be improved, please raise an issue.

Team culture and diversity

Last Friday, I attended a course on managing people led by Meri Williams and learnt a lot. I highly recommend booking her next course if you can. During the Q&A session, there was a question about hiring for diversity and Meri had some very interesting thoughts. I won't try to reproduce them all here as I'll be doing her a disservice.

One comment that resonated was that ideally you want your team members to be able to see others like themselves in the organisation so they can see the potential for their future success and growth within the company.

She also pointed out that you need to ensure that your culture isn't exclusionary before the first hire that changes it. For example, let's say that the entire team always goes out for beers on Friday after work. As soon as you hire a father of young kids, he probably wants to go home on Friday at 5pm so that he can see them before they go to sleep. If you haven't already changed this aspect of your team's culture, then the new team member is blamed for Friday night beers no longer being the same. So not only is he the first family-man in the company, he's now responsible for "ruining" a tradition. Who would want to be that person? How long is he likely to stick around?

The same basic issue applies to everyone who doesn't fit the culture, whether they are a woman, black, over 35, deeply religious, transgender, etc.

Interestingly, this issue also came up in an article published the same day in The Guardian regarding GitHub usernames where Lorna Mitchell commented: "I want people to realise that the minorities do exist. And for the minorities themselves: to be able to see that they aren’t the only ones … it can certainly feel that way some days."

It's really important that you have someone "ahead" of you that you can see is a success. If you don't, then you're more likely to leave, both the company and the industry.

You can see this effect with user groups too. For example, I have children and have to plan around my family commitments when I go out to a meet up in the evening. If a user group announces the next meeting on Twitter or to the mailing list only a few days before it happens, then the odds are that I won't be able to go and the only people that do attend are those that don't have to plan their lives in advance. I know that the user group is not intentionally excluding me; it's the side-effect of their culture.

Obviously, you can't magic up a diverse set of senior developers overnight. However, you can address culture and behaviour in your company or user group that is exclusionary to anyone in a different demographic to your current team.

Use vim's :make to preview Markdown

As it becomes more painful to use a pointing device for long periods of time, I find myself using vim more and so I'm paying more attention to customisation so that the things I'm used to from Sublime Text are available to me.

One thing I'm used to is that when I run the build command on a Markdown file, I expect Marked for Mac to open and render the file that I'm writing. Vim has :make which by default runs make and is mapped to cmd+b on MacVim, so I just needed to reconfigure that command to do the right thing.

The easiest way to do this is via a file type plugin. These are files that live in ~/.vim/ftplugin and are named after the file type. In this case, the file is markdown.vim. The commands inside the file are then available whenever you're editing a file of that type. (You can use :set ft? to find out the file type of the current file.)

To configure what :make does, we set the makeprg setting like this:

set makeprg=open\ -a\ Marked\\\ 2.app\ '%:p'

Note that spaces need to be escaped for vim and then the space in "Marked 2.app" needs escaping for the shell, which is why there are three \s in a row.

Add this line to ~/.vim/ftplugin/markdown.vim and :make now opens Marked and life is just that little bit easier…

Obviously, if you're not on a Mac or use a different tool to preview Markdown, then configure appropriately!

PSR-7 file uploads in Slim 3

Handling file uploads in Slim 3 is reasonably easy as it uses the PSR-7 Request object, so let's take a look.

The easiest way to get a Slim framework project up and running is to use the Slim-Skeleton to create a project:

composer create-project slim/slim-skeleton slim3-file-uploads

and then you can cd into the directory and run the PHP built-in web server using:

php -S 0.0.0.0:8888 -t public public/index.php

Displaying the form

We can now create a simple form, firstly by setting up the / route in src/routes.php:

$app->get('/', function ($request, $response, $args) {
    // Render file upload form
    return $this->renderer->render($response, 'index.phtml', $args);
});

The view script, templates/index.phtml contains the form:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Slim 3</title>
        <link rel="stylesheet" href="http://yegor256.github.io/tacit/tacit.min.css">
    </head>
    <body>
        <h1>Upload a file</h1>
        <form method="POST" action="/upload" enctype="multipart/form-data">
            <label>Select file to upload:</label>
            <input type="file" name="newfile">
            <button type="submit">Upload</button>
        </form>
    </body>
</html>

Handling the upload

We now need to write the route that handles the uploaded file. This goes in src/routes.php:

$app->post('/upload', function ($request, $response, $args) {
    $files = $request->getUploadedFiles();
    if (empty($files['newfile'])) {
        throw new Exception('Expected a newfile');
    }

    $newfile = $files['newfile'];
    // do something with $newfile
});

The file upload in $_FILES is available from the $request's getUploadedFiles() method. This returns an array keyed by the name of the <input> element. In this case, that's newfile.

The $newfile object is a instance of PSR-7's UploadedFileInterface. Typical usage is to check that there is no error and then move the file to somewhere else. This is done like this:

if ($newfile->getError() === UPLOAD_ERR_OK) {
    $uploadFileName = $newfile->getClientFilename();
    $newfile->moveTo("/path/to/$uploadFileName");
}

There's also other useful methods such as getClientMediaType() and getSize() if you need them.

Conclusion

As you can see, dealing with file uploads within a PSR-7 request is really easy!