Category: Slim Framework

Stand-alone usage of Zend-InputFilter

Any data that you receive needs to be checked and validated. There are number of ways to do this including PHP's filter_var, but I prefer Zend-InputFilter. This is how to use it as a stand-alone component.

Installation

Firstly, we install it using Composer:

$ composer require zendframework/zend-inputfilter
$ composer require zendframework/zend-servicemanager

You don't have to have ServiceManager, but it makes working with InputFilter much easier, so it's worth installing.

Create the InputFilter

The easiest way to create an InputFilter is to use the provided Factory class. Let's consider an Author entity that has the properties: author_id, name, biography & date_of_birth. We can create an input filter like this:

use Zend\InputFilter\Factory as InputFilterFactory;

class Author
{
    protected $author_id;
    protected $name;
    protected $biography;
    protected $date_of_birth;

    // ...

    protected function createInputFilter()
    {
        $factory = new InputFilterFactory();
        $inputFilter = $factory->createInputFilter([
            'author_id' => [
                'required' => true,
                'validators' => [
                    ['name' => 'Uuid'],
                ],
            ],
            'name' => [
                'required' => true,
                'filters' => [
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                ],
            ],
            'biography' => [
                'required' => false,
                'filters' => [
                    ['name' => 'StringTrim'],
                    ['name' => 'StripTags'],
                ],
            ],
            'date_of_birth' => [
                'required' => false,
                'validators' => [
                    ['name' => 'Date'],
                    [
                        'name' => 'LessThan',
                        'options' => [
                            'max' => date('Y-m-d'),
                            'inclusive' => true,
                        ],
                    ],
                ],
            ],
        ]);

        return $inputFilter;
    }
}

The createInputFilter() method takes an associative array where the key is the name of the input and then the value is a specification. There are a number of elements in the specification, but we usually just specify required, filters and validators.

required This can be either true or false. If false, then the validators do not execute, but the filters do.
filters An optional array of Zend-Filters. A filter modifies the supplied data before it is passed to the validators (if any). The filtered data is used by the rest of the application. In this example, we have added two filters: StringTrim & StripTags.
validators An optional array of Zend-Validators. A validator will test the filtered value for the input and fail if the data is not valid. If any validator fails, then the entire InputFilter is invalid.

This particular input filter requires that author_id and name are present, but that biography and date_of_birth are optional. The author_id must be a UUID, the name & biography must not have leading or trailing whitespace or no HTML tags and the date_of_birth, if present, must be a valid date in the past.

Using the InputFilter

To use the InputFilter, we set the data and then call isValid(). This can be done in a validate() method that looks like this:

Use Crell\ApiProblem\ApiProblem;
use Error\Exception\ProblemException;

Class Author
{
    // ...

    /**
     * Create an author
     *
     * @param  array $data
     * @return Author
     * @throws ProblemException
     */
    public static function createAuthor($data)
    {
        $inputFilter = $this->createInputFilter();
        $inputFilter->setData($data);

        if ($inputFilter->isValid()) {
            return new Author($inputFilter->getValues());
        }

        $problem = new ApiProblem('Validation failed');
        $problem->setStatus(400);
        $problem['errors'] = $inputFilter->getMessages();

        throw new ProblemException($problem);
    }
}

In this case, it's an API, so the data has come from a PUT or POST request. We call setData() to pass the array of data into the InputFilter and then call isValid(). If the data is valid, we can return a newly instantiated Author object that is constructed with the filter data. If the validation fails, then we throw a ProblemException which needs an ApiProblem instance, so we create one for it.

To find out which validators failed, getMessages() provides a nested array which is very useful for passing back to the API client.

As an example, this is what failure looks like:

$ curl -i -X "POST" "http://localhost:8888/authors" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d $'{ "name": "", "author_id": "1234" }'

HTTP/1.1 400 Bad Request
Host: localhost:8888
Connection: close
X-Powered-By: PHP/7.0.14
Content-type: application/problem+json

{
    "errors": {
        "author_id": {
            "valueNotUuid": "Invalid UUID format"
        },
        "name": {
            "isEmpty": "Value is required and can't be empty"
        }
    },
    "title": "Validation failed",
    "type": "about:blank",
    "status": 400
}

Fin

That's all there is to it. Zend-InputFilter is a very flexible data filter and validator and works really well for APIs, such as those written in Slim.

Rendering problem details in Slim

As I've already noted, in the project I'm currently building, I'm rendering errors in my Slim Framework API using RFC 7807: Problem Details for HTTP APIs via Larry Garfield's ApiProblem component and rka-content-type-renderer.

One place where we need to integrate this approach into Slim is in the error handlers. Let's look at NotFound. To ensure that we return a response in the right format, we need to implement our own NotFound handler:

src/App/Handler/NotFound.php:

<?php
namespace App\Handler;

use Crell\ApiProblem\ApiProblem;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class NotFound
{
    /**
     * Invoke not found handler
     *
     * @param  ServerRequestInterface $request  The most recent Request object
     * @param  ResponseInterface      $response The most recent Response object
     *
     * @return ResponseInterface
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
    {
        $problem = new ApiProblem(
            'Not Found',
            'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html'
        );
        $problem->setStatus(404);

        $renderer = new RKA\ContentTypeRenderer\ApiProblemRenderer();
        return $renderer->render($request, $response, $problem);
    }
}

The NotFound handler must return a Response. To do this, we create a new ApiProblem object with the title "Not Found" and set the type to HTML page that defines the 404 status code. We then instantiate the ApiProblemRenderer and call its render method. The renderer will then return a Response object in either XML or JSON based on the Accept header, with the correct Content-Type header. We then set the status code and return it.

To register our new handler, we use the container. If you're using the skeleton application as your base, then this goes in dependencies.php:

src/dependencies.php:

// Error handlers
$container['notFoundHandler'] = function () {
    return new App\Handler\NotFound();
};

Slim will now use our handler whenever a NotFoundException is raised.

This is it in action:

$ curl -i -H "Accept: application/xml" http://localhost:8888/foo
HTTP/1.1 404 Not Found
Host: localhost:8888
Connection: close
X-Powered-By: PHP/7.0.14
Content-type: application/problem+xml

<?xml version="1.0"?>
<problem>
  <title>Not Found</title>
  <type>http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html</type>
  <status>404</status>
</problem>

We can of course apply this to the other error handers: NotAllowed, Error & PhpError which all follow the same pattern as we have done for NotFound.

Handling JSON data errors in Slim 3

When you send JSON data into a Slim Framework application with a content-type of application/json, then Slim will decode it for you if you use getParsedBody():

Using curl to test:

If there's an error however, you get this:

If you care about this, you can use json_last_error_msg() and return an error:

(note – in real code, you should check that the Accept header was a JSON oneā€¦)

Don't forget the JSON_PRETTY_PRINT as a human is going to be reading this error, so make it easier for them.

Use jsonlint for more information

If you really want to provide great diagnostics, then use jsonlint:

Update your handler like this:

(lint() will return NULL or a ParsingException, so we don't need to test for anything else.)

The result looks like this:

This is much more informative!

Checklist for releasing Slim

Release process for Slim so that I don't forget any steps; based on a check list created by Asgrim. I should probably automate some of this!

Preparation:

  • Ensure all merged PRs have been associated to the tag we're about to release.
    Find them via this search: [is:pr is:closed no:milestone is:merged].
  • Close the milestone on GitHub.
  • Create a new milestone for the next patch release.
  • Ensure that you have the latest master & that your index is clean.
  • Find the ID of the current milestone. This is the numeric id found in the URL of the milestone detail page (e.g. 34).
  • Generate the changeling using changelog_generator & copy to clipboard:
    changelog_generator.php -t {your-github-api-token} -u slimphp -r Slim -m {ID} | pbcopy

Tag and release:

  • Edit App.php and update the VERSION constant to the correct version number. Commit the change.
  • Tag the commit: git tag -s {x.y.z} & paste the change log generated above into the tag message.
  • Update App.php again and set the VERSION constant to {x.y+1.0}-dev & commit.
  • Push the commits and the new tag: git push --follow-tags
  • Go to the releases page and click "Draft a new release":
    • Enter the tag name and ensure that you see a green tick and "Existing tag" appear.
    • Set the release title to the same as the tag name.
    • Paste the change log generated above into the release notes box (it is already formatted with Markdown).
    • Click "Publish release".
  • Write announcement blog post for website & publish.

Filtering the PSR-7 body in middleware

Sometimes, there's a requirement to alter the data in the Response's body after it has been created by your controller action. For example, we may want to ensure that our brand name is consistently capitalised.

One way to do this is to create middleware that looks like this:

This works perfectly, so if my response body contains "I consult through my company, nineteen feet.", then the output of the $brandFilter middleware is "I consult through thought my company, Nineteen Feet.", which is exactly what we want.

That's great, but what happens if the new string is shorter than the old one? For instance, suppose I want to replace all uses of "nineteen feet" with "19FT".

Using the same middleware, but changing the str_ireplace call with:

creates this output:

That's not quite what we wanted! This result is because the new string is shorter, so the characters from the old string are still in the PHP stream and as we haven't change the length, they are still there. The PSR-7 StreamInterface doesn't allow us access to the underlying stream, so we can't call ftruncate on it.

Hence, the easiest solution is to replace the Response's PSR-7 StreamInterface object with a new one containing what we need:

The output is now what we expect:

Slim 3.4.0 now provides PSR-7!

I've been neglecting Slim's PR queue recently, so this weekend I dedicated a lot of time to merging all the good work that our contributors have done. As a result, I'm delighted to release version 3.4.0!

This release has a larger set of changes in it than I would have ideally liked which is a direct consequence of having gone two months between releases rather than one.

One particularly interesting addition that we have a made this release is adding a provide section to our composer.json file:

This means that we have informed Composer that Slim provides a valid implementation of the interfaces in psr/http-message-implementation virtual package that defines the PSR-7 interfaces.

This means that when you install a Composer package that requires psr/http-message-implementation in your Slim project, then Composer will now recognise that Slim satisfies this requirement and won't insist you install another PSR-7 implementation just for package resolution!

There's lots of other goodies in 3.4.0, so check out the release notes and upgrade!

DI Factories for Slim controllers

When using classes for route actions in Slim 3, I recommend using a single class for each route. However you can use a single class for multiple routes.

To register a class method to a route you pass a string as the route callable where the class name is separate from method by a colon like this:

Slim will retrieve MyController from the DI container and then call the listAction method using the usual signature:

function (Request $request, Response $response, $args = []) : Response;

If you don't specify a method, then Slim will see if it treat the class as a callable, so you can implement __invoke() and then register the route like this:

and Slim will call MyController::__invoke() for you.

Writing a DI factory for your class

Usually, your controller action will need some dependencies in order to work, such as access to a service layer class, or ORM's entity manager.

To handle this, you should inject the dependency in your controller's constructor by writing a factory for the DI container. This sounds scary and complicated, but a factory is just another way of saying "a function that instantiates an object". This is the simplest DI container factory we can write for MyController:

The closure is the factory and as you can see, it simply returns an new instance of MyController. It is registered with Slim's default DI container by assigning the closure to an array key (['MyController']) and it is vital that the string you use here is the same as the string you use before the colon in the route configuration ('MyController:list'.

Injecting the dependencies

To inject the dependencies, we register them with the DI container too as factories and then retrieve them in our controller factory.

Firstly, register a dependency:

Now we can use this in our controller factory. To do this note that the factory closure has a parameter, $c, which is the DI container itself. This means we can retrieve anything that's registered with the DI container by using the get() method.

Hence we update our controller factory like this:

The MyController constructor now receives our dependency and can store it to a class property ready for use in the route action method like this:

There are numerous advantages to doing this. The main one for me is that there are no surprise dependencies any more. You can look at the constructor and know exactly which classes this class needs to do its job. You can also test it more easily which is beneficial!

I prefer to use one class for each route action as I can ensure that the dependencies that are injected are the correct ones for this action. When using multiple action methods in a controller class, you start needing to inject classes that are only used for just one or two of the actions and this is inefficient, especially if those dependencies are relatively expensive to construct. There are ways around this if you use a more powerful DI container such as Zend-ServiceManager though.

Overriding Slim 3's error handling

Slim 3 registers two error handers:

This means that if you want to override the default error handlers, you need to override both error handlers with your own code.

Each error handler is a callable. The signatures are:

  • errorHandler: function ($request, $response, $exception)
  • phpErrorHandler: function ($request, $response, $error)

To override a error handler, simply register a new callable with the Container:

If you don't want to repeat yourself, you can register the phpErrorHandler like this:

Note that you can register any PHP callable, so a class with an __invoke() method also works.

Handling PHP notices

As an aside, Slim 2's error handler catches PHP notices for you (which can be infuriating or very useful!). Slim 3 doesn't do this by default, so if you want to catch PHP notices then you need to register your own error handler like this:

That's all there is to it.

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:

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

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

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:

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

The callback then becomes:

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

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

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

We do the same for the view object:

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:

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!

Configuration in Slim Framework

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

Setting up

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

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:

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:

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

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:

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

config.php:

We then load using:

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:

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:

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:

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.