Slim-Csrf with Slim 3

In addition to the core Slim framework, we also ship a number of add-ons that are useful for specific types of problems. One of these is Slim-Csrf which provides CSRF protection.

This is middleware that sets a token in the session for every request that you can then set as an hidden input field on a form. When the form is submitted, the middleware checks that the value in the form field matches the value stored in the session. If they match, then the all is okay, but if they don't then an error is raised.

For the simplest use case, you need start the session and add the middleware:

session_start();
$app->add(new Slim\Csrf\Guard());

Then, from within a given route callable, you can create your form and add two hidden fields: one for the token's name and one for its value:

$app->get('/', function ($request, $response, $args) {
    // CSRF token name and value
    $name = $request->getAttribute('csrf_name');
    $value = $request->getAttribute('csrf_value');

    // Render a form
    $html = <<<EOT
<!DOCTYPE html>
<html>
<head><title>CSRF test</title></head>
<body>
    <form method="POST" action="/process">
        <input type="hidden" name="csrf_name" value="$name">
        <input type="hidden" name="csrf_value" value="$value">
        <input type="text" name="name" placeholder="Name">
        <input type="submit" value="Go">
    </form>
</body>
</html>
EOT;

    return $response->write($html);
});

If you run this in a browser and view the source, you'll see something like this:

Slim csrf view source

Refresh and you see different values for the csrf_name and csrf_value fields, which means that the user can have multiple tabs open and submit without any issues.

For testing, I created a simple route callable:

$app->post('/process', function ($request, $response, $args) {
    return $response->write("Passed CSRF check.");
});

Pressing form's submit button will result in the display of "Passed CSRF check.". If you then refresh and confirm the post, you'll see "Failed CSRF check!" and the HTTP status code will be 400.

Customising the CSRF failure

It's likely that you'll want to customise the CSRF failure display as a plaint text error message isn't very user friendly! To change this, supply a callable to the Guard class which has the same signature as middleware: `
function($request, $response, $next). The middleware must return a Response.

This allows you to supply a custom error page:

$guard = new Slim\Csrf\Guard();
$guard->setFailureCallable(function ($request, $response, $next) {
    return $response->write(<<<EOT
<!DOCTYPE html>
<html>
<head><title>CSRF test</title></head>
<body>
    <h1>Error</h1>
    <p>An error occurred with your form submission.
       Please start again.</p>
</body>
</html>
EOT);
});
$app->add($guard);

As the failure callable has the middleware signature, you can also set a flag into $request and then deal with the CSRF failure later. The failure callable would look something like this:

$guard->setFailureCallable(function ($request, $response, $next) {
    $request = $request->withAttribute("csrf_result", 'FAILED');
    return $next($request, $response);
});

Now, your route callable can decide what to do:

$app->post('/process', function ($request, $response, $args) {
    if (false === $request->getAttribute('csrf_result')) {
        // Deal with error here and update $response as appropriate
    } else {
        // successfully passed CSRF check
        $response->write("Passed CSRF check.");
    }
    return $response;
});

This is very powerful and remarkably easy to set up.

Summary

The flexibility of the failure callable allows you to handle a CSRF validation failure in the most appropriate way for your application and is a very powerful feature of this middleware.

As it's PSR-7 compliant, you can use the middleware independently of Slim with any PSR-7 middleware dispatch system that uses the middleware signature of function($request, $response, $next) where a Response is returned.

Using abstract factories with Slim 3

In my Slim 3 skeleton, I chose to put each action into its own class so that its dependencies are injected into the constructor. We then register each action with the DI Container like this:

$container['App\Action\HomeAction'] = function ($c) {
    return new App\Action\HomeAction($c['view'], $c['logger']);
};

In this case, HomeAction requires a view and a logger in order to operate. This is quite clear and easy. However, it requires you manually register each action class with the DI Container.

Bruno Skvorc said this on Twitter:

In short, this seems ULTRA wasteful if I want ALL my controllers to get view and logger.

Slim 3's default DI container is Pimple, which is one of the simpler DI Containers out there. To solve Bruno's problem, my initial though was to use Zend\ServiceManager's abstract factories feature.

Fortunately, Slim 3 supports container-interop and I've already written RKA\ZsmSlimContainer which integrates Zend\ServiceManager with Slim 3. Once we have ZSM in place it's all quite easy!

We need to register our own abstract factory with Zend\ServiceManager. An abstract factory requires two methods to be implemented: canCreateServiceWithName and createServiceWithName. The method names are fairly self-explantory.

In our case, canCreateServiceWithName needs to return true if the requested class name ends in "Action" and then createServiceWithName simply creates the class with the two required dependencies. It looks like this:

app/src/ActionAbstractFactory.php:

<?php
namespace App;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ActionAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        if (substr($requestedName, -6) == 'Action') {
            // This abstract factory will create any class that
            // ends with the word "Action"
            return true;
        }
        return false;
    }
 
    public function createServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $className = $requestedName;

        // This factory creates Actions that have precisely two
        // constructor parameters: $view & $logger
        $view = $locator->get('view');
        $logger = $locator->get('logger');
        
        return new $className($view, $logger);
    }
}

We then register the abstract factory with the DI container:

$container->addAbstractFactory(new App\ActionAbstractFactory());

The end result is that we don't need to register every Action with the DI Container individually as long as they all have the same constructor signature. This is obviously a simplistic example, but the principle applies to any situation where you need to create different classes in the same vein.

If you want to see this in action, I've created a sample project on GitHub.

Zend\Input fallback value

Recently an issue was reported against Zend\InputFilter where the reporter has discovered a regression where the fallback value wasn't being populated correctly. Matthew investigated, fixed it and asked me to review it.

I was fascinated as I didn't realise (or had completely forgotten!) that Zend\Input and Zend\InputFilter supported fallback values so I looked into it and it turns out that it's simple and works exactly as its name implies.

For the basic case of using an Input directly, you use it like this:

<?php
$name = new \Zend\InputFilter\Input('name');
$validators = $name->getValidatorChain();
$validators->addValidator(new \Zend\Validator\StringLength(5), true);

$name->setFallbackValue('Rob Allen');
$name->setValue('');

$isValid = $name->isValid();
var_dump($name->getValue());

The output is "Rob Allen".

That is, when the value supplied is empty, then, the fallback value is set instead and used.

A wrinkle

There is, however, a wrinkle! Sometimes $name->getValue() returns an empty string and this occurs due to particular combinations of required, allow_empty & continue_if_empty as I've already discussed.

If continue_if_empty is false then the combination of required & allow_empty affects whether you get the fallback value or an empty string when calling getValue() after isValid():

required allow_empty What happens
true false With an empty value, the fallback value is returned in getValue().
true true The fallback value is ignored and an empty value is returned in getValue().
false false The fallback value is ignored and an empty value is returned in getValue().
false true The fallback value is ignored and an empty value is returned in getValue().

If you compare this table to the one in the last post, you'll notice that in the three cases where the validators are not run, the fallback value is not set. This is not surprising as those combinations result in a short-circuit of isValid().

Test app

Again, I used a test application to check this out

<php
require 'vendor/autoload.php';

$values = [
    // [contine_if_empty, required, allow_empty]
    [false, true, false],
    [false, true, true],
    [false, false, false],
    [false, false, true],
    [true, true, false],
    [true, true, true],
    [true, false, false],
    [true, false, true],
];

foreach ($values as $row) {
    test(...$row);
}

function test($continueIfEmpty, $required, $allowEmpty)
{
    // set up Input with a StringLength validator so we'll know if the
    // validators have run as they will always fail
    $name = new \Zend\InputFilter\Input('name');
    $validators = $name->getValidatorChain();
    $validators->addValidator(new \Zend\Validator\StringLength(5), true);

    $name->setFallbackValue('Rob Allen');
    $name->setValue('');

    $name->setRequired($required);
    $name->setAllowEmpty($allowEmpty);
    $name->setContinueIfEmpty($continueIfEmpty);

    // Test
    echo "continue_if_empty: " . (int)$continueIfEmpty;
    echo ", required: " . (int)$required;
    echo ", allow_empty: " . (int)$allowEmpty;
    $isValid = (int)$name->isValid();
    echo " - Result: isValid() = $isValid";
    if (!$isValid) {
        echo " " . current($name->getMessages());
    } else {
        echo ", value = " . $name->getValue();
    }
    echo "\n";
}

As before, this app simply runs through all combinations of required, allow_empty & continue_if_empty against a Zend\InputFilter\Input with a fallback value set and sees what happens.

This is the output:

$ php test.php 
continue_if_empty: 0, required: 1, allow_empty: 0 - Result: isValid() = 1, value = Rob Allen
continue_if_empty: 0, required: 1, allow_empty: 1 - Result: isValid() = 1, value = 
continue_if_empty: 0, required: 0, allow_empty: 0 - Result: isValid() = 1, value = 
continue_if_empty: 0, required: 0, allow_empty: 1 - Result: isValid() = 1, value = 
continue_if_empty: 1, required: 1, allow_empty: 0 - Result: isValid() = 1, value = Rob Allen
continue_if_empty: 1, required: 1, allow_empty: 1 - Result: isValid() = 1, value = Rob Allen
continue_if_empty: 1, required: 0, allow_empty: 0 - Result: isValid() = 1, value = Rob Allen
continue_if_empty: 1, required: 0, allow_empty: 1 - Result: isValid() = 1, value = Rob Allen\

Zend\InputFilter

Note that with the fix discussed at the start of this post, Zend\InputFilter works exactly the same as Zend\Input, as you'd expect. This fix was back ported to the 2.4 release too, so if you are using fallback values, ensure that you're using the latest 2.4 or 2.5 version.

Conclusion

If you want to use a fallback value with Zend\Input make sure that you set required to true and allow_empty to false. Fortunately this is the default, so that's probably what you're doing anyway!

Zend\Input and empty values

I'm forever getting confused about how the combination of Zend\Input's required, allow_empty & continue_if_empty interact with an empty value, so I've decided to write it down.

These settings define what happens when you try to validate an empty value for a given input. For Zend\Input, empty means exactly equal to null, an empty string or an empty array.

Firstly, let's start with the three settings:

Setting Default What it does
required true When true, the value must not be empty (unless allow_empty is true).
allow_empty false When true, the field may be empty if it's required.
continue_if_empty false When true, the validators are run even if the field is empty.

Firstly, contine_if_empty controls if the validators are run for an empty value. If contine_if_empty is true, then the validators are always executed.

If contine_if_empty is false, then the decision on whether to run the validators depends on the combination of required and allow_empty:

required allow_empty What happens
true false For an empty value, the validators are run. isValid() is determined by the validators and is false as the NotEmpty validator fails. (This is the default)
true true For an empty value, the validators are not run. isValid() = true.
false false For an empty value, the validators are not run. isValid() = true.
false true For an empty value, the validators are not run. isValid() = true.

Test app

To prove this, I wrote this little test app:

 ?php
require 'vendor/autoload.php';

$values = [
    // [contine_if_empty, required, allow_empty]
    [false, true, false],
    [false, true, true],
    [false, false, false],
    [false, false, true],
    [true, true, false],
    [true, true, true],
    [true, false, false],
    [true, false, true],
];

foreach ($values as $row) {
    test(...$row);
}

function test($continueIfEmpty, $required, $allowEmpty)
{
    // set up Input with a StringLength validator so we'll know if the
    // validators have run as they will always fail
    $name = new \Zend\InputFilter\Input('name');
    $validators = $name->getValidatorChain();
    $validators->addValidator(new \Zend\Validator\StringLength(5), true);
    $name->setValue('');

    $name->setRequired($required);
    $name->setAllowEmpty($allowEmpty);
    $name->setContinueIfEmpty($continueIfEmpty);

    // Test
    echo "continue_if_empty: " . (int)$continueIfEmpty;
    echo ", required: " . (int)$required;
    echo ", allow_empty: " . (int)$allowEmpty;
    $isValid = (int)$name->isValid();
    echo " - Result: isValid() = $isValid";
    if (!$isValid) {
        echo " " . current($name->getMessages());
    }
    echo "\n";
}

You'll need to run composer require zendframework/zend-inputfilter if you want to test it yourself.

Interesting side note: I got to use the splat operator, so this is PHP 5.6 only code :)

The test is quite simple. We add a StringLength validator to an input so that we know that the validators have run as it will always fail. We then iterate through all the the combinations of required, allow_empty & continue_if_empty and see what happens.

The output is:

$ php test.php 
continue_if_empty: 0, required: 1, allow_empty: 0 - Result: isValid() = 0 Value is required and can't be empty
continue_if_empty: 0, required: 1, allow_empty: 1 - Result: isValid() = 1
continue_if_empty: 0, required: 0, allow_empty: 0 - Result: isValid() = 1
continue_if_empty: 0, required: 0, allow_empty: 1 - Result: isValid() = 1
continue_if_empty: 1, required: 1, allow_empty: 0 - Result: isValid() = 0 The input is less than 5 characters long
continue_if_empty: 1, required: 1, allow_empty: 1 - Result: isValid() = 0 The input is less than 5 characters long
continue_if_empty: 1, required: 0, allow_empty: 0 - Result: isValid() = 0 The input is less than 5 characters long
continue_if_empty: 1, required: 0, allow_empty: 1 - Result: isValid() = 0 The input is less than 5 characters long

Conclusion

The interactions between required, allow_empty & continue_if_empty aren't that complicated, but there's enough complexity that it's worth having a reference handy!

Checking your code for PSR-2

Most of the projects that I work on follow the PSR-2 coding style guidelines. I prefer to ensure that my PRs pass before Travis or Jenkins tells me, so let's look at how to run PSR-2 checks locally.

PHP_CodeSniffer

My preferred tool for checking coding styles in PHP is PHP_CodeSniffer. This is command line tool, phpcs, that you can run against any file.

PHP_CodeSniffer can test against a number of standards. The default is PEAR, so you must use the command line switch --standard=PSR2 in order for it to check against the right one.

Let's take an example from joind.in's web2 project:

$ phpcs --standard=PSR2 BaseApi.php 

FILE: ...joind.in/joindin-vm/joindin-web2/app/src/Application/BaseApi.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 11 | ERROR | [x] Expected 1 space after closing parenthesis; found 9
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 70ms; Memory: 6Mb

This is the code that's wrong:

if (isset($config['apiUrl']))
{
    $this->baseApiUrl = $config['apiUrl'];
}

The opening brace is in the wrong place.

If there is a note that PHPCBF can fix some validations automatically, then you can run the phpcbf command line tool to automatically fix your code:

$ phpcbf --standard=PSR2 BaseApi.php 
Changing into directory .../joindin-vm/joindin-web2/app/src/Application
Processing BaseApi.php [PHP => 1452 tokens in 186 lines]... DONE in 52ms (1 fixable violations)
        => Fixing file: 0/1 violations remaining [made 3 passes]... DONE in 163ms
Patched 1 file
Time: 246ms; Memory: 8Mb

And phpcbf has changed the code to be compliant:

if (isset($config['apiUrl'])) {
    $this->baseApiUrl = $config['apiUrl'];
}

Obviously, not all violations can be automatically fixed, but a good number can be.

Automating with Phing

Generally you want to be able to run phpcs across all your source files without too much effort and also get your CI tool to do the same. My preferred choice here is to use Phing.

Each command within a Phing build.xml file is called a target. The PHP_CodeSniffer target for the web2 project is:

<target name="phpcs">
 <phpcodesniffer standard="PSR2"
      description="Run PSR2 standards over the codebase"
      haltonerror="true">
   <fileset dir="${basedir}/app">
     <include name="**/*.php"/>
   </fileset>
   <fileset dir="${basedir}/tests">
     <include name="**/*.php"/>
   </fileset>
   <formatter type="full" usefile="false"/>
 </phpcodesniffer>
</target>

With this target we run phpcs over all PHP files in the `app` and `tests` directories. We run it like this:

$ phing phpcs

As Phing is simply a build tool, the error output is the same format as earlier.

Editor integration

I use Vim and Sublime Text 3, which both have plugins for checking syntax.

Syntastic for Vim

Syntastic is a great plugin for Vim that checks syntax for any language that you can think of. Install it using your preferred method and then for PHP linting and PSR-2 checking set up in your .vimrc like this:

" Syntastic
let g:syntastic_php_checkers=['php', 'phpcs']
let g:syntastic_php_phpcs_args='--standard=PSR2 -n'

You can also add Syntastic summary information to the status line using %{SyntasticStatuslineFlag()}.

The display looks like this (Yes, I like white backgrounds for editing, sorry!):

Syntastic PSR2 checking

When you save the file, Syntactic runs the checkers that you've set up, php -l and phpcs --standard=PSR2 -n in this case and displays any errors with a red S> in the gutter and if you've enabled it, a summary in the status line. When your cursor is on that line, then the error is displayed at the very bottom.

SublimeLinter for Sublime Text 3

The SublimeLinter project does for Sublime Text 3. You need to install "SublimeLinter", "SublimeLinter-php" and "SublimeLinter-phpcs" via Package Control as each linter is separate.

Configure by selecting the Preferences -> Package Settings -> SublimeLinter -> Settings - User menu item. My settings are:

{
    "user": {
        "delay": 0.5,
        "linters": {
            "php": {
                "@disable": false,
                "args": [],
                "excludes": []
            },
            "phpcs": {
                "@disable": false,
                "args": [
                    "-n"
                ],
                "excludes": [
                    "*.phtml",
                    "*.twig"
                ],
                "standard": "PSR2"
            }
        }
    }
}

This sets up SublimeLinter to check half a second after you stop typing and then it will highlight any errors:

SublimeLinter PSR2 checking

You get a marker in the left hand gutter and an outline around the detected error. Placing your cursor on that line displays the error in the status bar.

To sum up

Ensuring that your code matches the coding style for your project is very easy. If you run phpcs locally and fix any issues before you submit your PR, then the project maintainers will love you!

Custom OAuth2 authentication in Apiiglity

I have a client that's writing an Apigility API that needs to talk to a database that's already in place. This also includes the users table that is to be used with Apigility's OAuth2 authentication.

Getting Apigility's OAuth2 integration to talk to a specific table name is quite easy. Simply add this config:

'storage_settings' => array(
    'user_table' => 'user',
),

To the relevant adapter within zf-mvc-auth => authentication config.

However, if you want to use different column names, that's a bit trickier as they are hardcoded in the OAuth2\Storage\Pdo class. To get Apigility's OAuth2 components to look at the correct columns, you create your own OAuth2 Adapter. I chose to extend ZF\OAuth2\Adapter\PdoAdapter which extends OAuth2\Storage\Pdo and go from there.

ZF\OAuth2\Adapter\PdoAdapter extends the base class to add bcrypt hashing. This is good, so it's a good place to start from. I created a new module, MyAuth to hold my adapter and its factory. The adapter looks like this:

<?php
namespace MyAuth;

use ZF\OAuth2\Adapter\PdoAdapter;

/**
 * Custom extension of PdoAdapter to validate against the WEB_User table.
 */
class OAuth2Adapter extends PdoAdapter
{
    public function __construct($connection, $config = array())
    {
        $config = [
            'user_table' => 'legacy_user'
        ];

        return parent::__construct($connection, $config);
    }

    public function getUser($username)
    {
        $sql = sprintf(
            'SELECT * from %s where email_address=:username',
            $this->config['user_table']
        );
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array('username' => $username));

        if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            return false;
        }

        // the default behavior is to use "username" as the user_id
        return array_merge(array(
            'user_id' => $username
        ), $userInfo);
    }

    public function setUser($username, $password, 
        $firstName = null, $lastName = null)
    {
        // do not store in plaintext, use bcrypt
        $this->createBcryptHash($password);

        // if it exists, update it.
        if ($this->getUser($username)) {
            $sql = sprintf(
                'UPDATE %s SET pwd=:password, firstname=:firstName,
                    surname=:lastName WHERE username=:username',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        } else {
            $sql = sprintf(
                'INSERT INTO %s (email_address, pwd, firstname, surname)
                    VALUES (:username, :password, :firstName, :lastName)',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        }

        return $stmt->execute(compact('username', 'password', 'firstName',
            'lastName'));
    }

    protected function checkPassword($user, $password)
    {
        return $this->verifyHash($password, $user['pwd']);
    }
}

This code for getUser and setUser() is lifted directly from OAuth2\Storage\Pdo and all I've done is changed the column names. In this case I have email_address for my username, and pwd for the password column. Similar, I wrote my own checkPassword based on ZF\OAuth2\Adapter\PdoAdapter, again changing the array key to check to 'pwd'.

Now that we have the actual work done, we need to wire it into Apigility.

Firstly we need a factory so that the DIC can instantiate our adapter:

<?php
namespace MyAuth;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\Adapter\Driver\Pdo\Pdo as PdoDriver;

class OAuth2AdapterFactory implements FactoryInterface
{
    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return OAuth2Adapter
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $connection = $serviceLocator->get('DB\Master');
        if (!$connection->getDriver() instanceof PdoDriver) {
            throw new \RuntimeException("Need a PDO connection!");
        }

        $pdo = $connection->getDriver()->getConnection()->getResource();
        return new OAuth2Adapter($pdo);
    }  
}

This is fairly standard code. Note that the DB\Master is the name of the database connection that is set up in the Apigility admin. I've been a bit lazy and assume that it's a PDO based adapter. If it isn't, it'll blow up, so if you're not using PDO, then it won't work as is!

To register your new authentication adapter with Apigility, create a config file in config/autoload and call it myauth.global.php or something:

<?php
return [
    'zf-mvc-auth' => [
        'authentication' => [
            'adapters' => [
                'MyAuth' => [
                    'adapter' => 'ZF\\MvcAuth\\Authentication\\OAuth2Adapter',
                    'storage' => [
                        'storage' => 'MyAuth\OAuth2Adapter',
                        'route' => '/oauth',
                    ],
                ],
            ],
        ],
    ],
];

The adapter is called MyAuth and is now available to select in the API configuration pages of the admin:

Myauth apigility

To sum up

All in all, it's really easy to write custom OAuth 2 authentication for Apigility as it's a very flexible platform. I've simply changed the column names here, but it would be easy enough to write an adapter against a different storage system altogether, though you would have to override more methods and possibly start from a more appropriate base class.

Replacing Pimple in a Slim 3 application

One feature of Slim 3 is that the DI container is loosely coupled to the core framework. This is done in two ways:

  1. The App composes the container instance rather than extending from it.
  2. Internally, App depends on the container implementing the container-interop interface.

You can see this decoupling in the way that you instantiate a Slim 3 application:

$settings = [];
$container = new Slim\Container($settings);
$app = new Slim\App($container);

Slim 3 ships with Pimple by default, but my preference is for Zend\ServiceManager, so I decided to integrate ServiceManager into Slim 3.

RKA\ZsmSlimContainer is the result of this work.

Usage

To use RKA\ZsmSlimContainer, simply add it via composer:

composer require akrabat/rka-slim-zfsm-container

and then update your index.php:

$settings = [];
$container = new RKA\ZsmSlimContainer\Container($settings);
$app = new Slim\App($container);

As you can see, due to the decoupling, the change required is very small and everything still works! The only thing that doesn't work is Pimple\ServiceProviderInterface's register() method as that is tightly coupled to the Pimple container itself.

Implementation

To implement this, I extended Zend\ServiceManager and in the constructor registered Slim's default services. This is easy enough to do with ServiceManager's setFactory method. For example:

    $this->setFactory(
        'callableResolver',
        function ($c) { return new CallableResolver($c); },
        false
    );

In this example, the callableResolver service needs to return a new instance every time it is retrieved from the container, so I pass false in as the third parameter to setFactory as it will default to shared otherwise.

This was the minimum effort required, but I wanted to be able to use code that had been written for Pimple (as far as possible), such as Twig-View. To do this, I implemented ArrayAccess as that's how Pimple works. Implementing offsetGet, offsetExists and offsetUnset was easy as I simply had to call ServiceManager's get, has and unregisterService methods respectively, but offsetSet required a bit more work.

When setting a service via array access, you can assign settings, closures or instances:

$container['foo'] = 'a setting';
$container['bar'] = function($c) { return new Bar($c->get('foo)); }
$container['baz'] = new Bar('another setting');

In ServiceManager, you would use different methods, such as setService or setFactory.

I needed to detect what was intended and call the correct internal method, so I came up with this:

    public function offsetSet($id, $value)
    {
        if (is_object($value)) {
            if ($value instanceof \Closure) {
                return $this->setFactory($id, $value);
            }
            return $this->setService($id, $value);
        }

        if (is_string($value) && class_exists($value)) {
            return $this->setInvokableClass($id, $value);
        }

        return $this->setService($id, $value);
    }

I also support the invokable feature of ServiceManager, where if the value is a class name string, then ServiceManager can instantiate when required. This would be used like this:

$container['qux'] = \My\Fun\Thing::class;

 

All in all, I was pleased at how the decoupled nature of Slim 3 made it easy to replace Pimple with my preferred DI container. I particularly like how it is seamless enough that I can continue to use Twig-View.

Debugging PHP SOAP over SSL using Charles

I'm currently integrating against a SOAP server using PHP which wasn't working as I expected, so I wanted to find out what was happening over the wire. I have Charles installed and use it regularly with OS X's system-wide proxy settings. However, PHP's SoapClient doesn't use these, so I had to work out how to do it manually.

Enabling SoapClient to send via a proxy is really easy and is documented by Lorna Mitchell in Using Charles To Debug PHP SOAP:

$options = [
    "cache_wsdl" => WSDL_CACHE_NONE,
    "soap_version" => SOAP_1_1,
    "trace" => 1,
    "proxy_host" => "localhost",
    "proxy_port" => 8888,
];

$client = new \SoapClient($wsdl, $options);

I did this and saw traffic in Charles. However, my service endpoint is SSL and I saw this error:

PHP Fatal error:  SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://example.com/Service.svc?singleWsdl' : failed to load external entity "https://example.com/Service.svc?singleWsdl" in SoapServiceProcessor.php on line 167

Looking in Charles, I saw the note:

You may need to configure your browser or application to trust the Charles Root Certificate. See SSL Proxying in the Help menu.

Aha!

Again, we turn back to Lorna for how to do sort this out. This time, we need Manipulating HTTP with Charles Proxy, that she wrote for TechPortal. Unhelpfully, that website doesn't use section links, so scroll all way down to the Charles and SSL section to find out the relevant information about how to set up Charles for SSL proxying.

On OS X, you simply do:

  • Help -> SSL Proxying -> Install Charles Root Certificate
  • Proxy -> SSL Proxying Settings:
    • Check Enable SSL Proxying
    • Add the endpoint's domain to the list of locations

Finally, I needed to tell SoapClient to trust Charles' root certificate so that it can decrypt the SSL traffic.

This is done by downloading the Charles root certificate (Help -> SSL Proxying -> Save Charles Root Certificate) and storing it somewhere. I chose to put it in /usr/local/etc/charles-ssl-proxying-certificate.crt.

Finally, configure a new stream_context that knows about this certificate and add it to the SoapClient:

$options = [
    "cache_wsdl" => WSDL_CACHE_NONE,
    "soap_version" => SOAP_1_1,
    "trace" => 1,
    "proxy_host" => "localhost",
    "proxy_port" => 8888,
    "stream_context" => stream_context_create([
        'ssl' => [
            'cafile' => '/usr/local/etc/charles-ssl-proxying-certificate.crt'
        ]
    ];
];

$client = new \SoapClient($wsdl, $options);

Now everything works and I can see the actual data that's being sent to the SSL SOAP service and I solved my problem!

First beta of Slim Framework 3

Last night, I tagged beta 1 of Slim Framework 3! This is a significant upgrade to v2 with a number of changes that you can read on the Slim blog.

For me, the two key features that I'm most excited about are:

  • PSR-7 support, along with the standard middleware signature of:
        function($request, $response, $next) { return $response; }
  • Dependency injection container with container-interop compliance. We ship with Pimple by default, but I'm planning to use Zend\ServiceManager.

There's lots of other changes and we believe we have kept to the key tenants of Slim, keeping it focussed as a micro-framework suitable for building any application that you want to build. Slim is a particularly good choice our new Composer-based world where you can pick the best of breed components to build your application.

Testing

The easiest way to get going with a Slim 3 application is to use my skeleton application:

$ composer create-project -n -s dev akrabat/slim3-skeleton my-app
$ cd my-app
$ php -S 0.0.0.0:8888 -t public public/index.php

and browse to http://localhost:8888. I wrote up some more about my skeleton application previously.

Alternatively, the README file has information on starting from scratch.

This is a Beta!

Obviously, this is beta release – we are sure there are bugs and issues to be improved. We really want you to test it and kick the tyres. Please report any issues you find to us. We also appreciate pull requests!

You can also find helpful people on IRC in the #slimphp channel on Freenode.

Also not that while we hope that we won't need to change any function signatures, we aren't promising that we'll keep BC before 3.0 stable.

Docs

The new docs are very much a work in progress! They are incomplete and out of date in places. Again, please report any issues and pull requests are very welcome!

Next steps

We shall be concentrating on documentation and ensuring that Slim 3 is as stable as we can make it. This is an exciting time and I'm looking forward to when Slim 3.0 final is released.

Selecting the service port with PHP's SoapClient

I'm currently integrating with a SOAP service which has two different services defined.

The relevant part of the WSDL is:

<wsdl:service name="Config">
    <wsdl:port name="BasicHttpBinding_IConfiguration" binding="tns:BasicHttpBinding_IConfiguration">
        <soap:address location="http://nonsecure.example.com/Configuration.svc"/>
    </wsdl:port>
    <wsdl:port name="BasicHttpsBinding_IConfiguration" binding="tns:BasicHttpsBinding_IConfiguration">
        <soap:address location="https://secure.example.com/Configuration.svc"/>
    </wsdl:port>
</wsdl:service>

I discovered that PHP's SoapClient will select the first port it encounters and doesn't provide a way to select another one. This was a nuisance as I wanted to use the SSL one.

Through research, I discovered that I can use __setLocation():

$client->__setLocation('https://secure.example.com/Configuration.svc');

However, I don't control that endpoint, so I would rather select based on the port's name.

As I couldn't find a way to get the data from SoapClient, I decided to parse the WSDL myself and pull the information out. I don't do a lot with XML namespaces, so had to look up how to handle them and then how to extract the right data using XPath.

As I had to look it up, I'm putting it here, so I can find it again more easily!

I converted my new found knowledge into a method to extract the location attribute from the <soap:address> element of the <wsdl:port> with the correct name element:

function getLocationForPort($wsdl, $portName)
{
    $file = file_get_contents($wsdl);

    $xml = new SimpleXmlElement($file);

    $query = "wsdl:service/wsdl:port[@name='$portName']/soap:address";
    $address = $xml->xpath($query);
    if (!empty($address)) {
        $location = (string)$address[0]['location'];
        return $location;
    }

    return false;
}

Xpath is ideal for this job!

Usage is simply:

$client = new SoapClient($wsdl);
$sslLocation = getLocationForPort($wsdl, 'BasicHttpsBinding_IConfiguration');
if ($sslLocation) {
    $client->__setLocation($location);
}
// work with $client as normal

Now, all my calls to the SOAP service are via SSL as they should be!