Do you need training or consultancy? Get in touch!

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.

Rendering ApiProblem with PSR-7

In the API I'm currently building, I'm rendering errors using RFC 7807: Problem Details for HTTP APIs. As this is a Slim Framework project, it uses PSR-7, so I updated rka-content-type-renderer to support problem.

RFC 7807 defines a standard for sending details of an error in an HTTP response message. It supports both XML and JSON formats. From the RFC, an example response is:

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
    "type": "https://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "instance": "/account/12345/msgs/abc",
    "balance": 30,
    "accounts": [
        "/account/12345",
        "/account/67890"
    ]
}

Only title and type are required, though status should also be set. Full information is in the RFC, which is one of the easier ones to read.

In PHP, Larry Garfield has created the crell/ApiProblem component. The code to implement the above message is:

Use Crell\ApiProblem\ApiProblem;

$problem = new ApiProblem("You do not have enough credit.", "http://example.com/probs/out-of-credit");
$problem
  ->setDetail("Your current balance is 30, but that costs 50.")
  ->setInstance("http://example.net/account/12345/msgs/abc");

$problem['balance'] = 30;
$problem['accounts'] = array(
  "http://example.net/account/12345",
  "http://example.net/account/67890"
);

$jsonString = $problem->asJson();
$xmlString = problem->asXml();

ApiProblemRenderer

The only tricky bit is working out if we need to send back JSON or XML. This is called content negotiation and we read the Accept header to find out what the client wants. However, the Accept header has a complicated format with quality levels as you can see from RFC 7321, section 5.3.2. Fortunately, we can use Negotiation by Will Durand to deal with this, which is how rka-content-type-renderer works.

rka-content-type-render now has an ApiProblemRenderer which will read the Accept header and work out the if the client would prefer JSON or XML. If it can't determine, it will default to JSON.

In Expressive or Slim, it's used like this:

use Crell\ApiProblem\ApiProblem;
use RKA\ContentTypeRenderer\ApiProblemRenderer;
use Zend\Diactoros\Response;

$app->get('/', function ($request, $response, $next) {
    $problem = new ApiProblem(
        'Unauthorised',
        'http://www.example.com/api/docs/authentication'
    );
    $problem->setStatus(403);

    $renderer = new ApiProblemRenderer();
    return $renderer->render($request, new Response(), $problem);
});

This is it in action, with an XML accept header:

$ curl -i -H "Accept: application/vnd.akrabat.api+xml" http://localhost:8888/
HTTP/1.1 403 Forbidden
Host: localhost:8888
Connection: close
X-Powered-By: PHP/7.0.14
Content-type: application/problem+xml

<?xml version="1.0"?>
<problem>
  <title>Unauthorised</title>
  <type>http://www.example.com/api/docs/authentication</type>
  <status>403</status>
</problem>

And with a JSON one:

$ curl -i -H "Accept: application/vnd.akrabat.api+json" http://localhost:8888/
HTTP/1.1 403 Forbidden
Host: localhost:8888
Connection: close
X-Powered-By: PHP/7.0.14
Content-type: application/problem+json

{
    "title": "Unauthorised",
    "type": "http://www.example.com/api/docs/authentication",
    "status": 403
}

Ideally, the client should specify application/problem+json in their accept header, but in practice, I've never seen that happen, which is why rka-content-type-renderer works out the preferred format based on the media types specified.

Making Slack accessible on macOS

I've written before about how I tend to use my Mac via the keyboard as much as possible to minimise pain in my arm.

Possibly the best application on macOS to facilitate this is Shortcat which enables me to point and click at any UI element in any native Mac app. I rely on it a lot and it makes nearly every app I use accessible to me.

Recently, Slack updated their desktop app so that it is now built on top of Electron which broke Shortcat compatibility and made me sad as clicking links in Slack meant getting out the Wacom tablet. However, recently, I discovered a way to re-enable it that may work for other Electron apps too.

There's a switch to Chromium, --force-renderer-accessibility, which turns on accessibility access!

This means that I can enable accessibility integration with Shortcat from the command line using:

This enables Shortcat to work as you can see here:

Accessible Slack

In this image, I have enabled Shortcat (the small text box at the bottom of the window) and then typed "slimphp". Shortcat has highlighted where I can click and by pressing return, I'll select the link I want and it will open in a browser.

Note though, that I’ve found that if I change teams then Shortcat cannot "see" anything until I type something (and then backspace to remove it), after which Shortcat then works for that team. It seems that this only needs to be done once per launch per team though.

I also use Alfred, so I have made a workflow to open Slack with this command line argument which means that I can start Slack in my normal way using Alfred and it's accessibility enabled.

This is the workflow: Run-Slack-with-Accessibility.alfredworkflow. I hope that it helps.

A note on framework performance

A question came up recently wondering why Slim Framework was around 5 times slower than plain PHP.

All frameworks are by definition slower than no-code as there's more going on.

i.e. an index.php of:

    <?php
    header('Content-Type: application/json');
    echo json_encode(['result' => 1]);

is going to be much faster than a Slim application with this code:

    use \Slim\App;
    include 'vendor/autoload.php';
    
    $config = include 'Config/Container.php';
    $app = new App($config);
    $app->get('/do-it', function($request, $response){
        return $response->withJson(['result' => 1]);
    });
    
    $app->run();

This is not an apples-to-apples comparison of course as the Slim application is doing a lot more than the plain PHP one. It supports routing so that it can respond to different URLs (even ones with dynamic parameters in that URL) and HTTP methods, along with the ability to handle errors. It also a set of separate classes so that you can replace specific parts of our code with your own if you want it to do something different while still using the rest of our code.

To actually compare the performance of Slim to plain PHP, you need to make the plain PHP version have the same functionality as the Slim version and you'll discover that the performance difference is much smaller. Of course, custom written, application specific code will always be faster than generic code though. Slim is a micro framework, so already is pretty tight (more to do though and something I'm looking at for 4.0), but by definition, it supports more routing features than you may need.

However, you have to write it all that code yourself and, to be honest, parsing requests, sending responses, dealing with URL routing, formatting errors based on the Accept header, and so on are solved problems that don't add any value to your app if you write them yourself. You're much better off concentrating your efforts on the parts of your app that are unique to your app as that's where it brings value.

The definitive resource on this is Paul Jones' articles. Start with A Siege On Benchmarks. This was back in 2009 and somehow he managed to not test Slim! However, even if you take the fastest framework he measured (ZF 0.2!), you can see that at 180.56 requests per second, that's around 4.5 times slower than the baseline PHP of 829.82 requests/second.

Finally, the speed of this code will pale in comparison to the rest of your application's code, especially if you access a database or a web s service!

I'm interested in making Slim the fastest it can be, but the benefits of having a reliable framework that provides good routing middleware functionality out of the box cannot be overstated.

Homestead per-project crib sheet

I wanted a drop-dead simple way to try and replicate a problem someone was having on the Slim forums. I couldn't reproduce with php -S which is my go-to for this sort of thing, so I thought I'd try Homestead.

I had recently listend to a Voices of the Elephpant episode with Taylor Otwell & Joe Ferguson where Joe mentioned that Homestead worked on a per-project basis too. I didn't know this, so tried it out. The docs are fine, but there's a lot there that covers the global installation option when I just want to get up and running on a per-project basis.This is my crib sheet:

1. Create project

We just need a project that uses Composer. You probably have one already. If not, Slim Framework is a good choice!

2. Add Homestead to the project

The make command creates VagrantFile and a Homestead.yaml for configuration.

3. Deal with IP address and hostname

By default, the Homestead vagrant box is set up on 192.168.10.10 with the hostname homestead.app. You can change this in Homestead.yaml.

Add the IP address to /etc/hosts. This only needs to be done once if you don't change the defaults.

All done

We're all done, so we can use vagrant up to run our new website Go to homestead.app in a browser to see it. To shut down, use vagrant halt or vagrant destroy.

Slim home page

Automatic OCR with Hazel and PDFPen

I have a useful scanner as part of my networked HP printer that will scan directly to a shared directory on my computer. Once there, I want the file to be renamed to the current date and the document OCR'd so that I can search it.

To do this, I use Hazel and PDFPen and this is a note to ensure that I can remember to do it again if I ever need to!

Firstly, rename the file. My scanner names each file with the prefix scan, so the Hazel rule is quite simple:

If all the following conditions are met:
	Name starts with scan

Do the following to the matched file or folder:
	Rename with pattern: [date created][extension]

This is the screenshot:

Hazel1

Having renamed the file, we can use PDFPen's AppleScript support to perform an OCR of the document:

If all the following conditions are met:
	Extension is pdf
	Date Last Modified is after Date Last Matched

Do the following to the matched file or folder:
	Run AppleScript embedded script

The embedded AppleScript is:

tell application "PDFpen"
	open theFile as alias
	tell document 1
		ocr
		repeat while performing ocr
			delay 1
		end repeat
		delay 1
		close with saving
	end tell
	quit
end tell

This is the screenshot of it in Hazel:

Hazel2

That's it. Scanning a document now results in a dated, OCR'd PDF file in my Scans folder.

Using Phive to manage PHPUnit

I recently came across the Phive project and have had a play with it. Phive is part of phar.io and is intended to manage development tools such as PHPUnit in preference to using Composer's dev dependencies. The main advantages of Phive are that it uses the phar file of the tool and only keeps one copy of each version rather than downloading a new copy into each project.

How it works

Phive stores one copy of each phar file within ~/.phive/ and then for each project, it creates a symlink. For example, to install PHPUnit into a project, you simply change directory to the root of your project and type:

$ phive install phpunit

This will download the phpunit phar file (if it's not already downloaded) to your ~/.phive directory, It will then create a tools directory in your project with the symlink to phpunit.phar.

$ ls -l tools
total 8
lrwxrwxr-x  1 rob  staff  42  3 Jan 07:34 phpunit -> /Users/rob/.phive/phars/phpunit-5.7.5.phar

You can now run PHPUnit using ./tools/phpunit, however if you would rather install to a different directory, e.g. bin, then use the --target switch:

$ phive install --target bin/ phpunit

The phive.xml file

Phive also creates a phive.xml file to keep track of what it has installed. You should add this file to your git repository and ignore the installed files in tools.

$ echo -e "phpunit" >> tools/.gitignore
$ git add phive.xml tools/.gitignore
$ git commit -m "Add phpunit via Phive"

Other users of the project can now install all the Phive tools using:

$ phive install

Installing for global use

Phive also supports global installation with the -g switch:

$ phive install -g phpunit
Phive 0.6.2 - Copyright (C) 2015-2017 by Arne Blankerts, Sebastian Heuer and Contributors
Downloading https://phar.phpunit.de/phive.xml
Copying phpunit-5.7.5.phar to /usr/local/Cellar/php71/7.1.0_11/bin/phpunit

You may need sudo privileges, but for my HomeBrew installation, this wasn't necessary.

GPG signatures required

Note that Phive only works with projects that also release a GPG signature for their tools. This is good security, but currently a significant limitation if you use anything else other than the few tools listed on phar.io.

The most significant missing tool for me is PHP_CodeSniffer. There's an open issue on their bug tracker but as it's been open for 6 months now, I assume that it isn't important for that project which is frustrating.

As such, this limits the usefulness of Phive for me today, but it's certainly a project to watch.

2016 in pictures

Another year has passed which gives me an excuse to to reflect on what's happened. As usual, I look at the photos that I've taken and frame my thoughts around them.

January

At the very end of January I visited Phoenix, Arizona to see Evan, Priscilla & other friends. I also attended FOSDEM again and spent a tourist day in Brussels.

The Europeans insisted on seeing the sun!Atomium


February

I was fortunate enough to speak at PHPUK again in February.

The conference is about to startStart of the second day

March

The highlight of March was visiting the NYMR to see Flying Scotsman.

Flying Scotsman from the linesideUp close

April

In April, my eldest drove a Lamborghini and now intends to buy one! It's important to be able to dream when you're 14. I also visited the NRM.

Eldest drove a Lamborghini!Mallard

May

May is the month of birthdays in our household. I also attended the StatusCode event in Nottingham

Happy Birthday!Andrew


June

Two conferences in June. I spoke at PHP South Coast and took my favourite photo of Andrew Smith ever! I also attended Lead Developer.

Best pose everMichael Lopp

July

July was the inaugural UK edition of REST Fest in Edinburgh. We also went on holiday where I attempted sunrise photos and ate lots of ice cream.

Mike talks about Conway's four lawsSunrise

August

Worcester Comic Con happened in August where we got to meet "our" Doctor! Unfortunately this was also the month when my dad was taken ill.

Georgina with her DoctorLearning table top gaming

September

September was about family and friends with an extended family gathering and a trip to Leeds to see friends. Dad was transferred to a closer hospital too and started to get better. September was also the start of conference madness that lastest to November, with a trip to Dublin for DrupalCon.

Dad & meA group of amazing people

October

October was the month of conferences when I spoke at PHPNW in Manchester, attended OSCON Europe and spoke at the Software Architecture conference, both in London! The other welcome news this month was that Dad came out of hospital.

Coffee breakAttendies to my talk!

November

November is Fireworks Night in the UK which we celebrated with friends. This year, we contributed wood for the bonfire as we've been tiding our garden. I also spoke at Velocity in Amsterdam as did my friend Kevin.

Bonfire nightKevin discussing how to survive the Grand National

December

As usual, the final month of the year found me in a pub with some of my oldest Internet friend from our MMORPG gaming group that I've been part of since 1999. I visited Northern Ireland for the first time. Family from America also visited; I haven't seen my cousin Leigh in 28 years and so had not met her husband or children, so this was very much a highlight.

BL Cronxmas meetupFamilies



All in all a good year, though I will try and space out my conferences a little better in 2017!

SSH keys in macOS Sierra

Now that I've upgraded to macOS 10.12 Sierra, I noticed that SSH required me to enter my passphrase to keys every time I used them. This was a surprise as it's not how 10.11 El Capitan worked.

This is how to fix it.

Firstly, add your SSH key's passphrase to the keychain using ssh-add -K ~/.ssh/id_rsa (or any other key file). You can now use your SSH key without re-typing the password all the time which is very handy for use with GitHub/GitLab/Bitbucket/etc.

You can add as many keys as you like and ssh-add -l will show you which keys are registered.

When you reboot, you'll notice that ssh-add -l is empty which is different from how it works on macOS 10.11 and earlier which automatically re-added the keys it knew about. In Sierra, Apple has changed it so that you now need to explicitly add the known identities to the ssh agent. This is done using ssh-add -A which you need to run every time you reboot.

To save having to do this, you can either add ssh-add -A to your ~/.bash_profile file or update your SSH config by editing ~/.ssh/config and adding:

SSH will now work as expected and you'll never need to reenter your passphrase once it has been added to the system keychain.