Be Strict! PhpSpec 2 Best Practices Extension Just Released!

logo
image source: phpspec.net. Disclaimer: I have no affiliation with the creators of phpspec. I am just a user giving an opinion.

I just ran into a nice feature left out in the issues of phpspec and couldn’t resist to create my extension for it.

The trend is more and more generating final classes, this can be quickly achieved by a template under .phpspec folder. However, why do we want to copy paste templates all over the place? It makes little sense if in our projects we have determined to a strict adherence to better practices.

So I adapted my extension from the previous blog post and added a way to override completely with templates located on the extension itself.

<?php
 
namespace Cordoval\BestPractices\PhpSpec;
 
use PhpSpec\Extension\ExtensionInterface;
use PhpSpec\ServiceContainer;
 
class BestPracticesExtension 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')
            );
        });
 
        $container->setParam('code_generator.templates.paths', array(
            rtrim(__DIR__, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'.phpspec',
        ));
    }
}

The way it is achieved is the following. PhpSpec has a way to override certain parameters. The template render mechanism first tries to use the renderer to get some content string rendered, if it is not found or the result is null then it moves into the default phpspec templates. So it first tries to check local project templates and under your home directory, and then it falls back to the phpspec package templates. To just lock the templates into our extension templates location we just override this parameter with our __DIR__/.phpspec location in our extension. Then just place the templates we want to override there and that is it.

We are very opinionated so here is the resultant package: cordoval/best-practice-extension

All you need is:

    // ...
    "cordoval/best-practice-extension": "~1.0@dev"
    "phpspec/phpspec": "~2.1@dev",
    // ...

Yeah, they don’t tag the package that often and we have templates moving after 2.0.1, sometimes we need help to follow best practices too :). So in any case with those requires you get it.

Then just add it to your phpspec.yml

extensions:
    - Cordoval\BestPractices\PhpSpec\BestPracticesExtension

And generate away! 🙂

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!

Visiting Phansible: Flint Web Ansible-based Vagrant Box Generator

Overnight I ran into this nice interesting project https://github.com/Phansible/phansible.
Don’t worry http://puphpet.com is not dead, at least not yet. Phansible still needs to support many more options before going mainstream. Users on puphpet side are many more so far and puphpet is being tested by many companies that have validated the generation. However Phansible is a very good promise built around ansible. And promises to get closer or even close the puphpet club in the future.

Phansible uses ansible recipe generation using twig and flint to generate from a webserver and some parameters or selections input into it a nice little zip file with your recipies and vagrant file. It generates playbooks, vagrantfiles, and varfiles.

You can just:

git clone git@github.com:Phansible/phansible.git
cd phansible
composer install
php vendor/bin/phpunit -c tests/phpunit.xml
php -S localhost:8080 -t web/

Though we are not very proud of the unit tests they exist and run fast. Though thoughts are being put together to improve that lacking side. Testing an app based on flint or pimple or such that inject the app into itself or such things is a bit weird as it is not inherently decoupled.

The project source code also contains a little gift which is the ansible folder. Yes the files used to deploy the same site :). This and how things get generated also for new projects will teach you a lot about ansible even if you don’t know anything about it.

When you want to generate things just go to the website or your local box deployed:

Screenshot 2014-09-10 05.40.23

vagrant up
 
// ...
 
 
PLAY [all] ********************************************************************
 
GATHERING FACTS ***************************************************************
ok: [192.168.33.99]
 
TASK: [init | Update apt] *****************************************************
ok: [192.168.33.99]
 
TASK: [init | Install System Packages] ****************************************
changed: [192.168.33.99] => (item=curl,wget,python-software-properties)
 
TASK: [init | Add ppa Repository] *********************************************
changed: [192.168.33.99]
 
TASK: [init | Update apt] *****************************************************
ok: [192.168.33.99]
 
TASK: [init | Install Extra Packages] *****************************************
changed: [192.168.33.99] => (item=vim,imagemagick)
 
TASK: [nginx | Install Nginx] *************************************************
changed: [192.168.33.99]
 
TASK: [nginx | Change default nginx site] *************************************
changed: [192.168.33.99]
 
TASK: [php5-fpm | Install php5-fpm and php5-cli] ******************************
changed: [192.168.33.99] => (item=php5-fpm,php5-cli)
 
TASK: [php5-fpm | Set permissions on socket - owner] **************************
changed: [192.168.33.99]
 
TASK: [php5-fpm | Set permissions on socket - group] **************************
changed: [192.168.33.99]
 
TASK: [php5-fpm | Set permissions on socket - mode] ***************************
changed: [192.168.33.99]
 
TASK: [phpcommon | Install PHP Packages] **************************************
failed: [192.168.33.99] => (item=php5-cli,php5-curl,php5-imagick,php-pear,php5-cgi,php5-common,php5-dbg,php5-enchant,php5-fpm,php5-gd,php5-geoip,php5-gmp,php5-imap,php5-interbase,php5-intl,php5-ldap,php5-mapscript,php5-mbstring,php5-mcrypt,php5-memcache,php5-memcached,php5-odbc,php5-pspell,php5-readline,php5-recode,php5-snmp,php5-sqlite,php5-svn,php5-sybase,php5-tidy,php5-xcache,php5-xdebug,php5-xmlrpc,php5-xsl) => {"failed": true, "item": "php5-cli,php5-curl,php5-imagick,php-pear,php5-cgi,php5-common,php5-dbg,php5-enchant,php5-fpm,php5-gd,php5-geoip,php5-gmp,php5-imap,php5-interbase,php5-intl,php5-ldap,php5-mapscript,php5-mbstring,php5-mcrypt,php5-memcache,php5-memcached,php5-odbc,php5-pspell,php5-readline,php5-recode,php5-snmp,php5-sqlite,php5-svn,php5-sybase,php5-tidy,php5-xcache,php5-xdebug,php5-xmlrpc,php5-xsl"}
msg: No package matching 'php5-mbstring' is available
 
FATAL: all hosts have already failed -- aborting
 
PLAY RECAP ********************************************************************
           to retry, use: --limit @/Users/cordoval/playbook.retry
 
192.168.33.99              : ok=12   changed=9    unreachable=0    failed=1
 
Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.

I got an error, but it is just probably because some updates were missing on the extensions.

There are some PRs about including frameworks however the best bet if you want to generate your own boxes and custom stuff is to clone and deploy privately a version of this. This way you can have your own recipies included. Though i strongly encourage you to share them with the rest of us!

Remember this is just the first stage of generation, you now can fix the generated files by hand, don’t forget to git init and ignore the .vagrant and other generated folders. After the fix just rerun the vagrant up or vagrant reload and then voila!

Personally I don’t use vagrant when I am a solo consultant but only when I work on a team environment. I am trying to improve a box generated by puphpet via phansible generated files now. Notice they will include Erika Heidi and other’s improvements so it is good to follow good practices from people that work with vagrant a lot more!

There are good practices also for writing ansible files here http://www.reinteractive.net/posts/167-ansible-real-life-good-practices

Encouragements and for now this is it, I will try to share more as I come more acquainted with phansible generation and testing boxes. Don’t forget to retweet please!

Ain’t gonna need the Symfony bundle!

Often we think a bundle is the solution to our problems and we try hard to fit it into our app. Then developing an app becomes developing hacks to work around things extra-nous to our domain into our domain forcefully or as some could say “cleverly”.

Screenshot 2014-09-04 10.37.44

Lately I did a PR to a repo of an angularjs and RESTful implementation with the FOSRest libraries from symfony. But then I got this response to my PR:

Screenshot 2014-09-04 10.48.19
https://github.com/bayne/symfony-angular-todomvc/pull/1

So to try to prove the point of keeping it simple I went ahead and discovered some nice features from heroku. It will become certainly one of the tools i use if I can pay for it since development is faster, compilation is faster, everything is faster, even I am thinking running jolici or running tests suite in heroku if possible.

I had to use the buildpack for php from @chh and you can literally build the app clicking your iphone.

+        },
+        "heroku": {
+            "framework": "symfony2",
+            "compile": [
+                "cd web; ./../node_modules/.bin/bower install"
+            ]

This thing that was the big reason why plugging a bundle to plug composer hooks was a simple line of code added to the composer thanks to the buildpack. Everything else went quickly in the flow using bower.io and just the tools we know about.

Check the app, I can’t believe it is so easy 🙂

http://here-quick-cordoval-3.herokuapp.com/#/

Now you can PR and show it is working with one click:

Screenshot 2014-09-04 11.30.45

Enjoy!

Free HTML, CSS, PHP, JavaScript editor (IDE) – Codelobster PHP Edition

Screenshot 2014-09-04 08.50.16

In the world of open source softwares Codelobster is an integrated development environment which is fulfilling the requirements of all newbie’s in web programming world. Being a fresher if we have some freeware in hand then we can start our work without many thoughts. Obviously if the start is nice one can go far.

Some commendable features of Codelobster:

  • To highlight the code for different file tyes Codelobster has different color schemes. Being a developer I find it a tremendous feature to have highlighted code to easily differentiate it in complex coding.
  • Codelobster is providing auto completion feature for PHP, HTML, CSS and Javascript, along with HTML5 and CSS3. Full concentration on logical aspect is very necessary when one wants to do extensive coding.
  • Pressing F1 will open help for tags and attributes. There is plenty of information available. For few supported languages Context help is also available.
    Code based variable values can be seen using PHP debugger. By debugging; developer can look for errors and proceed further.
  • After successful coding it is also necessary to deploy code on remote servers and Codelobster offers FTP support.
  • Codelobster comes with portable versions that means there is no need to install it. One can immediately start using it just by keeping it on system disk.
  • Along with data exporting feature Codelobster provides base for CRUD operations. This can be achieved using SQL manager which highlights sql files and also the auto completion.
  • To relate code pages; Codelobster has inspector functionality like we see in Mozilla plugin.
    Codelobster is offering few more nice features like: navigating files & functions along with their descriptions by keep pressing the CTRL key, block selection possibilities, tool-tips, code collapsing, highlighting of pairs, bookmarking, file-view & folder structuring along with browser preview.
  • Few special plugins to work with are following:
  • JavaScript libraries: JQuery, Node.js
  • CMS: Drupal, WordPress , Joomla
  • Template engines: Smarty and Twig
  • PHP frameworks: Phalcon, CodeIgniter, Yii, CakePHP, Laravel, Symfony

Developer Site: http://www.codelobster.com/