Having a go at creating a Behat 3 extension

Ever since I got accepted to do a tutorial on Test legacy apps with Behat at the PHPNW15 conference, I’ve been meaning to look into creating custom extensions for Behat.

I didn’t have enough time to research into this while preparing for the tutorial, so left it in my todo list in Trello.

During the PHPNW15 long weekend (Friday - Monday), at some point over lunch I was at the table where Matt Brunt (@themattbrunt) and Ciaran McNulty (@ciaranmcnulty) were having a conversation about this and Ciaran said (paraphrasing) “In order to be able to write an extension, you really need to understand how Behat works.”

So a few months later, sleeves up and I went into my vendor/bin/behat and started looking (and poking) around.

Behat is a great application. Nice and clean code. And everything happens by magic - well, nearly. :)

But… I won’t be going into that.

I ended up taking a shortcut. I found a great article by its creator Konstantin Kudryashov (https://gist.github.com/everzet/9888598), and spent a lot of time looking into two existing extensions:

I’ve actually used mostly the structure of the Laravel extension.

The most important thing to keep in mind is that everything is being driven by Interfaces.

Following the structure of the Laravel extension, we have three main folders:

  • Context
  • Driver (won’t cover this here)
  • ServiceContainer

ServiceContainer/BehatExtension.php

This class implements Behat\Behat\Context\ServiceContainer\ContextExtension and requires/implements the following methods:

  • getConfigKey() - The extension config key
  • initialize(ExtensionManager $extensionManager) - used to hook into the configuration of other extensions e.g. Mink
  • process(ContainerBuilder $container) - You can modify the container here before it is dumped to PHP code.
  • configure(ArrayNodeDefinition $builder) - Setups configuration for the extension.
  • load(ContainerBuilder $container, array $config) - Loads extension services into temporary container.

configure(ArrayNodeDefinition $builder)

This method enables you to set the configuration for the extension. The parameters you need and their default values. For the Slim 3 extension, I used the config_file location (settings.php) which together with the dependencies_file is pretty much all I need to load the Slim/App and populate the DI container.

Adding the parameters in behat.yml

See the implementation on Github: https://github.com/pavlakis/slim-behat-extension/blob/master/src/ServiceContainer/BehatExtension.php#L42

load(ContainerBuilder $container, array $config)

This method lets you pass the configuration to your service in order to load it.

The $config array gives you access to extension configuration parameters (in behat.yml) e.g. $config['config_file'] and $config['dependencies_file']

We can use those values to load our Service through the $container.

The ContainerBuilder is used to:

  • Create an instance of the Service and pass it to the builder using $container->set(..)
  • Add a Definition containing the:
    * The Service and its link to its own extension class implementing ContextInitializer
    * A tag definition for the Service
  • Context/KernelAwareInitializer.php

    This class implements Behat\Behat\Context\Initializer\ContextInitializer and requires/implements the following methods:

    initializeContext(Context $context) - Initializes provided context.

    The constructor makes the Service/Kernel available. In the initializeContext(Context $context) method we check if the Context passed is an instance of the context we are expecting (in this case Pavlakis\Slim\Behat\Context\KernelAwareContext which also implements Behat\Behat\Context\Context - but it is specific to our extension) and if it is, we add our Service to the Context.

    https://github.com/pavlakis/slim-behat-extension/blob/master/src/Context/KernelAwareInitializer.php#L50

    Pavlakis\Slim\Behat\Context\App.php (Trait)

    This class is a Trait. And very important.

    It makes the app available through the FeatureContext class by just calling $this->app

    To use it, implement Pavlakis\Slim\Behat\Context\KernelAwareContext

    And that’s it. These are the points that I spent most time on. Hope it helps.

    This extension is available available through packagist: https://packagist.org/packages/pavlakis/slim-behat-extension

    And Github:
    https://github.com/pavlakis/slim-behat-extension

Making a CLI Request in Slim 3

I’m using @akrabat’s Slim 3 skeleton for the PHPMinds website, and I was looking for a way to make a Request through the CLI to use for crons and workers. But I wanted to call it the same way as a normal request.

php public/index.php /status GET event=true

DVO Media had a nice way of doing it in index.php: https://github.com/dvomedia/rest-skeleton/blob/master/web/index.php#L7

With Slim 3 using middleware, I thought that may have been a tidier way to do it, as I don’t like to add any logic in my index.php

I thought initially I would add it to the route. That didn’t work. It was too late in the request cycle.

I checked on irc (#slimphp) and @akrabat advised me to put it in the Middleware section. That did feel a much cleaner way to do it, and it worked!

In the middleware section (middleware.php): $app->add(new App\Middleware\CliRequest());

And the Middleware itself:

A generic vagrant template using ansible

Most of the PHP projects I work on have a very similar structure. That’s why I created this template so I can reuse it across most projects.

The initial Ansible package has originated from Phansible.com which is an awesome tool for easy generating vagrant provisioning using ansible.

In order for this to be a good learning exercise, I created a github repository for the generic vagrant template using ansible where I added branches for each configuration step with clear commits along the way.

As an overview, these are the steps I’ve taken.

  • (1) Generate the initial template from Phansible.com
  • (2) Added Ansible tasks to import files in order to create the database schema and also to populate the database.
  • (3) Created composer.json and added a task to run composer.
  • (4) Created a basic hello world application with a namespace that uses composer’s autoloading.

If you’re just interested for the final implementation, go straight to branch step4

Getting started with Composer and PSR-4

Getting started with PSR-4 and composer in your projects

For comments, go to gist.github.com/pavlakis/8810829#file-gistfile1-php

For completeness, I've included one of my comments below:

For efficiency, we shouldn't be including the tests directory on the main namespace. Instead, we can add it to the autoloader but only in the phpunit's bootstrap. (thanks to @noginn for his suggestion)

We can adapt the above example by making three changes:

  1. In composer.json remove the reference to tests/, save and run composer update
  2. Create MyApp/tests/bootstrap.php
  3. $autoloader = require DIR . '/../vendor/autoload.php';
    $autoloader->add('MyApp\Tests\', DIR);
  4. Change the bootstrap reference in MyApp/tests/phpunit.xml.dist to: bootstrap.php

 

The above example is available through github: https://github.com/pavlakis/psr4-with-composer

Quick script tests

You know how it is. There is some logic you want to test quickly before implementing it as part of your script.

One way to do it would be to create a new file just to test that.

An easier way, is to do it using interactive shell.

To start:

php -a

To end:

exit