Back to blog home

How to build an extension for PHPSPEC

I recently launched a new extension for PhpSpec, something that was a lot of fun, but also made me realise that building an extension allows you to learn so much more about the tool you are building for and about design philosophies. I want to share my experiences to offer a reference point for other engineers thinking about building an extension for their tool of choice.

 

About myself

I've been working with PHP for over 10 years. I also spent a year as a Ruby developer and was introduced to RSpec and Cucumber, but eventually came back to the PHP community to happily find PhpSpec and Behat. Now I try to find ways of making each tool more useful or at least try to add value wherever I can.

 

So how did Brobdingnagian come to be?

The first question, and the one I try to answer every day, is “what is good code?"

I hear a lot about best practice, but it isn’t always clear what the code should look like when this is achieved. I also hear a lot of questions about whether it is possible to be consistent across projects and framework, and whether there is a need for reasonable limits so a developer does not become overwhelmed by the pressure to achieve best practice.

Like all good stories, this started out as a flippant remark. “Someone should write an extension that analyses your code and tells you what class is too big,” I said.

My colleague voiced his support and suggested that the extension should be called Brobdingnagian. Brob.. Ding.. what?

It’s a word to use to describe size in Gulliver’s travels, he explained. To me this was a good enough an explanation; although perhaps not the easiest name to pronounce, I like the fact it identifies the package clearly.

Thirty minutes later my empty git repo was tweeted. If that doesn't spur you on nothing will.

 

The response was positive. I had enough interest to take a punt and attempt to build the extension.

Developing an extension

I can't stress the importance of planning and deciding what functionality you would like to add before you begin to build. Not just because the functionality may be out there already, but because it's also easy to get carried away from your extensions’ core functionality. Feature creep hurts. My advice is to keep it focused and concentrate on your core product.

I decided I would build the core class detection functionality before adding the Naming helper or Responsibility splitter.

First, you need to initialise a new Composer file that gives your extension a package name. Once this has been created, you need to add PhpSpec and finally add the autoloader directive to allow PhpSpec to create your files. And that's it! You are set up. Run Composer install and get going!

Going boldly...

Turns out you can start developing your extension using PhpSpec straight away, but what happens next, once you have something ready to test?

First implement the extension interface so you can instruct PhpSpec to register your class services (refer to the extension to see what I did with mine). PhpSpec will hand you control of the ServiceContainer to allow you to add your own service or override your own methods to interact with the Listener or other components in PhpSpec.

PhpSpec has a way to hook into its Listener and add Commands. By registering your extension you are given a handle to the ServiceContainer, containing all of PhpSpec’s core services. I used the Listener and Command objects to allow the extension to interact with where you are.

 

Travis ci

I quickly found I kept breaking the early builds during testing. I decided to have the project on Travis CI from the start, which would keep me honest, especially when it broke and displayed on my git repo. In an effort to keep it somewhat in line with PhpSpec, I used the PHP version settings it uses for testing.

 

Supported versioning

I took the decision to only support PHP on security release cycle versions. This means that although the core PhpSpec package supports 5.3 and above, I wanted to help encourage people to upgrade where possible and will also help to keep this project in line with PHP 5.5 and above (at the time of writing). This would also minimise any issues I could face in development.

However, I will ensure that as and when the project pushes forwards, I leave a final tagged release version for PHP that isn't supported.

It should still be possible to use the extension on 5.4, but I won't consciously develop for it. To be fair, it is my opinion that if you are using a version of PHP that is no longer supported then you have bigger problems then detecting if your class size and dependencies are too large.

 

Testing the tester

One of the cumbersome parts of building an extension (and I've not found a better way in other languages yet) is to create a dummy project that installs your extension with PhpSpec. I could test the extension on itself, but I want to see how an end user would interact with it. The best way to achieve this is to start a project and build some test scenarios.

Create a Composer file for a dummy project. If you are using a framework like Symfony, you can forgo the autoloader directives for its own one.

"repositories": [
    {
        "type": "vcs",
        "url": "/path/to/extension/locally"
     }
],
"require": {
    "elfiggo/brobdingnagian-detector":"dev-master"
},
"require-dev" : {
    "phpspec/phpspec":"^2.2"
},
"minimum-stability":"dev",
"autoload": {
    "psr-0": {
        "Brob": "src/"
    }
}

 

After installation you now add it to a phpspec.yml file

extensions:
    - Elfiggo\Brobdingnagian\Extension

 

Later on you can add parameters and get them from the ServiceContainer as well.

brobdingnagian:
    class-size: 300
    method-size: 15
    number-of-methods: 5
    dependencies: 3
    list-brob: true|false
    create-csv: true|false
    number-of-interfaces: 3
    number-of-traits: 1

 

It can be noted that there is an easier way to do this via Packagist.

 

Using packagist

The easiest thing to do would be to shove it up on Packagist and install it every time from there. The problems I caused myself by having to point to a local repo and to ask people to try it out are not worth it.

 

Testing

I found out that the simplest thing I could do to get started was actually not to worry about hooking it into PhpSpec, but to install it and write a spec. Once you have a class with a method that had been built by tests, it helps guide you towards your solution. It may take you a while to let go of the build first, and retrofit the ‘tests later’ mentality, but you will be OK.

 

The first spec written

class ClassSizeSpec extends ObjectBehavior
{
    const LESS_THAN_300 = 299;

    function it_does_not_complain_if_the_class_size_is_not_too_large(ReflectionClass $sus)
    {
        $sus->getEndLine()->willReturn(self::LESS_THAN_300);
        $this->check();
    }
}

 

Detectors

It seemed like a good idea to try to detect if a class size or the methods were too big. It was also suggested that the size of dependencies would also be helpful to highlight. Finally I felt that adding a number of methods would also help, although the size of class vs number of methods would be an interesting correlation.

PhpSpec allows you to reflect the Class it is testing which is great. Unfortunately its’ the Spec Object and not the real class and so in the Detectors I had to find and reflect the real one.

The next step is to start querying the class for information: what line do you end on, how many arguments does each method take, and how many methods are there. This led to an interesting revelation.

When you get the number of methods on a class that extends another class, they are included in the total. I could change this behaviour, but it might be worth looking at this as an opportunity to refactor your class towards interfaces and composition.

I also realised it was counting the number of private and protected methods; I feel it would be right to add filtering and so it's now on the list of upcoming features.

 

Params

Params is the object that holds the current default parameters for class, method and other sizes and options, you can change the defaults by adding parameters to your phpspec.yml file. This is the interesting part if you are in a team - together you will have to decide what ‘good’ looks like.

I think it's important to state these are defaults trying to help legacy systems move forwards, and I would not begin to suggest what ‘good’ would look like on your own projects. Enjoy the team debate!

 

Reporting

Reporting on the errors initially just threw exceptions. Even I found this tiresome as my suite would not run as I worked through exception after exception. I decided to add an ability to the PhpSpec runner which would allow users to list all the reported detections and display them at the end of their spec run.

I soon realised that it would be useful to have the reported detections in a format that could be managed properly. So I added the ability to generate a file to review with your team at a later stage.

 

Benefits and value

My goal here was to give the end user an extra level of analysis of their code without the need to use a third party tool.

For those who want to push their coding skills further by adding constraints of good design and practice, then I believe this tool can help.

 

Releasing 0.1.0-Alpha

I'll admit I had reservations about releasing this extension so early. The code hadn't gone through Scrutinizer, there could be inconsistencies in test results, and what if it simply didn’t work? Well after two weeks of developing the core, I had to let go and allow for criticisms.

Brobdingnagian was released 24 days after I began building it and to date has been cloned over 55 times.

 

The future

Stabilising Brobdingnagian is now a priority before adding the Class responsibility namer. I am currently researching the best options to achieve this, and deciding if it should be the responsibility of this extension. I will then need to find a way to hook into PhpSpec rerunner to allow users to split the class and methods if they are too large.

 

Whistle stop tour

This was a brief overview of the way I set up and built my extension, but if you’re thinking of building your own feel free to reach out to the extension community for help.

There are so many extensions yet to be written to enhance PhpSpec. I look forward to seeing what you can add.

As a final note I'm also happy for any contributions to this project, and would welcome your input. Feel free to ask any questions directly to me on Twitter: @Elfiggo.

 


 

GitHubhttps://github.com/Elfiggo/brobdingnagian-detector-phpspec-extension

Author's favourite extension: https://github.com/phpspec/nyan-formatters

More on extensions: http://www.phpspec.net/en/latest/cookbook/extensions.html