PhpSpec ~2.0.1: Create your Extension like a Pro!

So I was motivated to tackle and create my own extension for PhpSpec but I did not know which use case I would be able to pull. I noticed quickly that the templates were a bit too simple so I extend my templates under .phpspec folder of my project to be something like:

// .phpspec/specification.tpl
 
    /**
     *
     */
    public function %name%(%arguments%)
    {
        return 0;
    }

First thing to notice is the idea with PhpSpec is that I will be testing behavior and expecting some kind of return value, be that an object or something boolean or such. So I rather just put the return since it will be there anyways. And because these methods are public i will also do a docblock and annotate the interface describing what it does. It sounds like what you want to do as good practice.

And this one below, take a look at this one because this one is taking advantage of the wonderful notation MyClass::class to provide the FQCN of the class in a short hand:

// .phpspec/specification.tpl
<?php
 
namespace %namespace%;
 
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use %subject%;
 
class %name% extends ObjectBehavior
{
    function let()
    {
        $this->beConstructedWith();
    }
 
    function it_is_initializable()
    {
        $this->shouldHaveType(%subject_name%::class);
    }
}

This is not the default template specification.template. there is a difference. I am using the short notation to place the subject class namespace without quotes into the argument of shouldHaveType. This will allow to keep the line very short and I am adding the use statement at the top.

It will just rock in action, you will see! 🙂

Now as I was going through the code of PhpSpec, I saw the setting up of generator services yey:

    /**
     * @param ServiceContainer $container
     */
    private function setupGenerators(ServiceContainer $container)
    {
        $container->setShared('code_generator', function (ServiceContainer $c) {
            $generator = new CodeGenerator\GeneratorManager();
 
            array_map(
                array($generator, 'registerGenerator'),
                $c->getByPrefix('code_generator.generators')
            );
 
            return $generator;
        });
 
        $container->set('code_generator.generators.specification', function (ServiceContainer $c) {
            return new CodeGenerator\Generator\SpecificationGenerator(
                $c->get('console.io'),
                $c->get('code_generator.templates')
            );
        });

So on my extension I could just replace this SpecificationGenerator that inside looks like:

namespace PhpSpec\CodeGenerator\Generator;
 
use PhpSpec\Locator\ResourceInterface;
 
/**
 * Generates spec classes from resources and puts them into the appropriate
 * folder using the appropriate template.
 */
class SpecificationGenerator extends PromptingGenerator
{
    // ...
 
    /**
     * @param ResourceInterface $resource
     * @param string            $filepath
     *
     * @return string
     */
    protected function renderTemplate(ResourceInterface $resource, $filepath)
    {
        $values = array(
            '%filepath%'  => $filepath,
            '%name%'      => $resource->getSpecName(),
            '%namespace%' => $resource->getSpecNamespace(),
            '%subject%'   => $resource->getSrcClassname()
        );
 
        if (!$content = $this->getTemplateRenderer()->render('specification', $values)) {
            $content = $this->getTemplateRenderer()->renderString($this->getTemplate(), $values);
        }
 
        return $content;
    }

Notice that this is the sucker that does the computation of the spec name and classnames and the same values for the source. But see that we are missing the name of the class getName() available in the resource. That we need to be able to just paste the name of the class and then the ::class invention.

What I did? well I created my extension class, and change that service:

<?php
 
namespace Cordoval\PhpSpec;
 
use PhpSpec\Extension\ExtensionInterface;
use PhpSpec\ServiceContainer;
 
class CordovalExtension implements ExtensionInterface
{
    /**
     * @param ServiceContainer $container
     */
    public function load(ServiceContainer $container)
    {
        $container->setShared('code_generator.generators.specification', function (ServiceContainer $c) {
            return new ClassNotationSpecificationGenerator(
                $c->get('console.io'),
                $c->get('code_generator.templates')
            );
        });
    }
}

And in my ClassnotationSpecificationGenerator I do:

$values = array(
            '%filepath%'  => $filepath,
            '%name%'      => $resource->getSpecName(),
            '%namespace%' => $resource->getSpecNamespace(),
            '%subject%'   => $resource->getSrcClassname(),
            '%subject_name%' => $resource->getName(),
        );

That is it, now my generated Specs look like:

<?php
 
namespace spec\Cordoval\Bus;
 
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Cordoval\Bus\CommandBus;
 
class CommandBusSpec extends ObjectBehavior
{
    function let()
    {
        $this->beConstructedWith();
    }
 
    function it_is_initializable()
    {
        $this->shouldHaveType(CommandBus::class);
    }
}

Which is to say a bit more interesting and keeps on saving you tons of time!

2 thoughts on “PhpSpec ~2.0.1: Create your Extension like a Pro!

  1. Nice 🙂 I was actually wondering how to improve this because using strings does not allow for easy refactoring.

    As your only adding a ‘%subject_name%’ maybe you can open a PR to add this to the upstream so you do not have to repeat yourself every time 😉

Leave a Reply to Sebastiaan Stok Cancel reply

Your email address will not be published. Required fields are marked *