Skip to main content

Validating your PHP code on the fly

21 Jan 2014
Tags: grunt phpspec zf2 php

It's amazing how much time you can loose while running PHP tests. First you alter your code or test. Next you start your test and wait for it to finish. Finally you find out you messed up and start the process all over again. In this article I am going to show you how to automate PHP testing, so you can focus on adding business value to your application.

For this article I created a simple Zend Framework 2 application with Phpspec tests. Even though this article focuses on these 2 packages, it is possible to create this kind of automation for every possible package.

Application structure

First, let's take a look at the directory structure. In the representation below, you can see a default ZF2 skeleton. The root folder contains the directories config, module, public and vendor. The `config` folder provides the application configuration. In the `module` folder you can see 2 modules that should be tested: Export and Import. Next you can find the public directory which will bootstrap the application. And finally there is the vendor folder that will be filled with dependencies by Composer.

.
|-- config
|   |-- application.config.php
|-- module
|   |-- Export
|   |   |-- config
|   |   |-- spec
|   |   |-- src
|   |   |  `-- Export
|   |   |       |-- Service
|   |   |       `-- Module.php
|   |   |-- view
|   |   `-- Module.php
|   `-- Import
|       |-- config
|       |-- spec
|       |-- src
|       |  `-- Import
|       |       |-- Service
|       |       `-- Module.php
|       |-- view
|       `-- Module.php
|-- public
|-- vendor

Configuring composer

Because nobody wants to download all the dependencies manually, we will be using composer to get the required packages. We also want to configure composer to automatically load our custom modules: Export and Import. This way, no additional autoloading is required and Phpspec will be able to find all classes by itself.

composer.json

{
    "name": "veewee/validating-on-the-fly",
    "require": {
        "zendframework/zendframework": "2.*",
    },
    "require-dev": {
        "phpspec/phpspec": "dev-master",
        "fabpot/PHP-CS-Fixer": "*",
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "autoload": {
        "psr-0": {
          "Export": "module/Export/src/",
          "Import": "module/Import/src/"
        }
    }
}

As you can see, composer will download Zend Framework 2 to the vendor directory. When you add the option `--dev` during the installation, Phpspec and PHP-CS-Fixer will also be downloaded. The last part of this configuration file will provide autoloading for the custom Export and Import module. To download and install all the dependencies, one command is being used:

~$ composer install --dev --prefer-dist

Configuring phpspec

As you can see in the directory overview, there is already a spec folder available. The Phpspec tests will be placed in this directory. This way, every module has it's own scope and can be tested separately.

Now the tricky part: let's tell Phpspec where to find the custom source and spec files. Because we need to test multiple modules on multiple locations, we need to add multiple test suites to Phpspec. This is done by creating a phpspec.yml file in the root folder of the application. A test suite describes where the source and spec files of a custom namespace are located. Let's take a look at the configuration file for our application:

phpspec.yml

formatter.name: dot
suites:
  Export:
    namespace: Export
    src_path: 'module/Export/src/'
    spec_path: 'module/Export/'

  Import:
    namespace: Import
    src_path: 'module/Import/src/'
    spec_path: 'module/Import/'

One of the downsides of Phpspec is that, at the moment, you can not run suites based on suite name. To test this configuration, you can run following commands:

~$ ./vendor/bin/phpspec run module/Export/src
~$ ./vendor/bin/phpspec run module/Import/src

Configuring php-cs-fixer

Everybody wants clean, readable and maintainable code. That is why the code of the modules should be validated against the desired PHP standards. In this case, we want to check our classes against the PSR2 standard. Per module, you should add a file called .php_cs in the root directory of the module. In this file you can tell the Php-CS-Fixer which paths to check and add some default configuration settings.

.php_cs

<?php
$finder = Symfony\CS\Finder\DefaultFinder::create()
    ->exclude('language')
    ->exclude('view')
    ->exclude('config')
    ->in(__DIR__ . '/src')
    ->in(__DIR__ . '/spec');

$config = Symfony\CS\Config\Config::create();
$config->fixers(Symfony\CS\FixerInterface::PSR2_LEVEL);
$config->finder($finder);
return $config;

To test this configuration, you can run following commands:

~$ ./vendor/bin/php-cs-fixer fix module/Export --dry-run
~$ ./vendor/bin/php-cs-fixer fix module/Import --dry-run

Automate the testing process

Before the automation, every time you change a file in your module, you need to run Phpspec and occasionally the Php-CS-Fixer to validate your code. Because you have to define which module to check every time you run the command, you will spend some time searching for the right command in your console. A better way is to automate this process, so that a file change will trigger the command to test your code. This will save you some time and will give you an indication of when and where you messed up. What was that tool again to automate tasks? Exactly: Grunt!

There are some grunt plugins that can be used to automate PHP validation. In this article we are using Phpspec, but there are also plugins for other testing libraries like Phpunit and Behat. In the next part, I will tell you how to test your code on syntax errors, coding standard errors and bad functionality.

Grunt-Phpspec

For validating bad functionality we use grunt-phpspec. This tool will run Phpspec on our custom modules. For every PHP module we need to check, an entry in the configuration is required. Here is the sample configuration for the current project:

phpspec: {
  options: {
    prefix: './vendor/bin/'
  },
  export: {
    specs: 'module/Export/src'
  },
  import: {
    specs: 'module/Import/src'
  }
}

To run spec tests, you can use following commands:

~$ grunt phpspec
~$ grunt phpspec:export
~$ grunt phpspec:import

Grunt-php-cs-fixer

The tool for validating PHP coding standards is grunt-php-cs-fixer. As the name suggests: it does not only check for issues, it also fixes them. Therefore it must be possible to validate and fix errors with Grunt. This can be done by adding the parameter `--fixcs`. Here is the sample configuration for the current project:

// Place this parameter before grunt.initConfig() method:
var fixCs = grunt.option('fixcs') || false;

// Place this configuration in grunt.initConfig() method:
phpcsfixer: {
  options: {
    bin: 'vendor/bin/php-cs-fixer',
    verbose: true,
    dryRun: !fixCs,
    ignoreExitCode: fixCs,
    level: 'psr2',
    standard: 'Zend'
  },
  export: {
    dir: 'module/Export'
  },
  import: {
    dir: 'module/Import'
  }
}

To run the cs fixer, you can use following commands:

# Validating:
~$ grunt phpcsfixer
~$ grunt phpcsfixer:export
~$ grunt phpcsfixer:import

# Fixing:
~$ grunt phpcsfixer --fixcs
~$ grunt phpcsfixer:export --fixcs
~$ grunt phpcsfixer:import --fixcs

Grunt-Phplint

To validate PHP files for syntax errors you can use the commnd `php -l`. For automation, there is also a Grunt plugin available named grunt-phplint. This tool is added to the test suite to make sure the CS-fixer and spec tests only run when there is no syntax error. Here is the sample configuration for the current project:

phplint: {
  export: ['module/Export/**/*.php'],
  import: ['module/Import/**/*.php']
}

To run PHP lint, you can use following commands:

~$ grunt phplint
~$ grunt phplint:export
~$ grunt phplint:import

Grunt-watch

Now that we configured all tasks to validate the PHP code, we want the tests to run every time we change a class or spec test in our module. This is where grunt-contrib-watch comes in to play. This task will listen for file changes and will configured tasks for these files. When one of the tasks fail, the other tasks will not run. This is why I decided to run the tests in the order: lint, csfixer, phpspec. Here is the sample configuration for the current project:

watch: {
  export: {
    files: ['module/Export/**/*.php'],
    tasks: ['phplint:export', 'phpcsfixer:export', 'phpspec:export']
  },
  import: {
    files: ['module/Import/**/*.php'],
    tasks: ['phplint:import', 'phpcsfixer:import', 'phpspec:import']
  }
},

To start the watcher and automate the tests, you only have to use one command:

~$ grunt watch

Run all tests in one

For Continious Integration it would be nice if all tests could be run in one command. Well off course this is possible! It is just one line of configuration:

grunt.registerTask('test', ['phplint', 'phpcsfixer', 'phpspec']);

To start the complete test, you can use following command:

~$ grunt test

Conclusion

As you can see there are some tools to configure. Once the tools are configured, this set-up will save you some valuable time while testing your code. I highly recommend you to use a similar set-up because it is very easy to use yet very powerful. You won't regret!

whois VeeWee

Selfie

Hi there!

Glad you made it to my blog. Please feel free to take a look around. You will find some interesting stuff, mostly about web development and PHP.

Still can't get enough of me? Quick! Take a look at my Speakerdeck, Twitter, PHPC.Social, or Github account.