Using Grunt with PHP Quality Assurance Tools

Show­ing off using Grunt and the PHP Qual­i­ty Assur­ance Tool­chain to lim­it code smell in PHP

The irony is not lost on me, that is cer­tain. The lan­guage I work in the most is PHP, but with hav­ing grown a very soft spot for Grunt, I’ve been forced to learn how to use Node­JS in a more effi­cient man­ner, in par­tic­u­lar for writ­ing tasks and git hooks. I’ve men­tioned pre­vi­ous­ly how my git hooks are built, but this par­tic­u­lar arti­cle will not be touch­ing on that, but rather on var­i­ous tasks used as wrap­pers around tools that exists in the PHP sys­tems.

So, why Grunt? Why my insis­tance to shoe­horn every­thing into it? Well, in my defense, it’s not every­thing I’m try­ing to shoe­horn into it, but rather all tasks that should be repeat­able. It’s not a case of me being too “lazy” to remem­ber that to lint all of my files I just need to run find . -name "*.php" -exec php -l {} \; | grep "Parse error", it’s that it’s a waste of time that could’ve been spent bet­ter. Pri­or to find­ing Grunt, I would’ve either made it as an alias, or save it as a bash script, but both of those have the issue of porta­bil­i­ty. Using Grunt allows me to focus more on the actu­al cod­ing — serv­er-side or client-side — and less on ensur­ing that I’m check­ing syn­tax, tests, etc.

Quality Assurance Tools

Now, a dis­claimer: I’ve not man­aged to ful­ly imple­ment all of these tools into my work­flow. Some of them have well-writ­ten grunt tasks, and those will be not­ed. Oth­ers have aban­doned grunt tasks, and those I will note with an alter­nate way of get­ting a more use­ful task until it is no longer aban­doned, and the final cat­e­go­ry had no grunt tasks, so I’ve writ­ten a basic one that fits what­ev­er options seemed like­ly for me as I looked over how it’s used. Those can all be found — along­side the oth­er code for this guide — on Github, under the direc­to­ry tasks. The file struc­ture in the Gruntfile fol­lows exact­ly that repos­i­to­ry.

The set of tools I’ve imple­ment­ed have all been col­lect­ed from the PHP Qual­i­ty Assur­ance Tool­chain, where rather than using the PEAR Installer we will use Com­pos­er. Each tool will be installed local in the project that gets built up.

Assumptions

Obvi­ous­ly, you need to have installed Grunt. You also need to have cURL installed, which you prob­a­bly do, espe­cial­ly if you fol­lowed my above-men­tioned arti­cle to install Grunt, oth­er­wise it’s still easy: sudo apt-get install curl. Final­ly, you’ll need to have installed Git, as the PHP project we will be work­ing on is the basic Lar­avel start­ing page.

As far as PHP, you will need ver­sion 5.3.7+, with:

  • mCrypt
  • intl
  • XSL
  • Graphviz

sudo apt-get install php5-mcrypt php5-intl php5-xsl graphviz

Task categories

There are two spe­cif­ic sets of tasks that are being intro­duced. The first one is what I’d refer to as “block­ing” tasks. They will throw an error if their con­di­tions aren’t ful­filled, and I use them in my pre-commit git hook, ensur­ing that I don’t com­mit any­thing with errors in it.

The sec­ond set of tasks are prob­a­bly to be ran occa­sion­al­ly, at what­ev­er inter­val is deemed prac­ti­cal for your par­tic­u­lar set­up. They will just report on what­ev­er it is they’re deal­ing with, and though it’s out­side the scope of this guide, they should prob­a­bly be used for sta­tis­tic and oth­er things. Maybe a nice set of graphs show­ing how the com­plex­i­ty of the project grows over time?

Setup

As men­tioned above, the base for the PHP (because I don’t want to write a whole bunch of PHP just to show­case tasks) is going to be Lar­avel. Go to what­ev­er direc­to­ry you’re want­i­ng to work in and clone the repos­i­to­ry (I’m assum­ing the same name of the repos­i­to­ry as mine: phpqa­tools-grunt): git clone git@github.com:laravel/laravel.git phpqatools-grunt. Be aware that not every­thing is set up prop­er­ly just yet, we’ll get to the final bits of it fur­ther down.

You may also want to run git remote rm origin to remove the knowl­edge that the project has of the Lar­avel repos­i­to­ry it is clone from. I at least find the “Your branch is ahead of ‘origin/master’ by …” mes­sage to be untidy.

Gitignore

Open .gitignore and replace the cur­rent one with the fol­low­ing:

/bootstrap/compiled.php
/vendor
composer.phar
.DS_Store
Thumbs.db
*.log
/node_modules

.*
!.gitignore
!.jshintrc
!.bowerrc
!.editorconfig
!.travis.yml
!.gitattributes
!.htaccess
*~

Package.json

After that, we’ll set up the Node­JS part of the project required for Grunt. If you run npm init it will guide you through a set of options, or you can just cre­ate a new file called package.json in the root of the project, with the fol­low­ing con­tent:

{
  "name": "phpqatools-grunt",
  "version": "0.0.0",
  "description": "Showing off using Grunt and the PHP Quality Assurance Toolchain to limit code smell in PHP",
  "private": true,
  "scripts": {
    "test": "grunt test --verbose"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/Melindrea/phpqatools-grunt.git"
  },
  "author": "Marie Hogebrandt",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/Melindrea/phpqatools-grunt/issues"
  },
  "devDependencies": {
    "time-grunt": "~0.2.3",
    "load-grunt-tasks": "~0.2.0",
    "grunt": "~0.4.2",
    "grunt-jsvalidate": "~0.2.2",
    "jshint-stylish": "~0.1.4",
    "grunt-contrib-jshint": "~0.7.2",
    "grunt-jsonlint": "~1.0.4"
  }
}

As you can see, it includes a cou­ple of basic depen­den­cies, most of which are there to check that the var­i­ous con­fig files are valid. Two par­tic­u­lar sec­tions I want to draw your atten­tion to clos­er:

"scripts": {
    "test": "grunt test --verbose"
  }

Once you have a grunt file, if you run npm test, it will run the grunt test task ver­bose­ly. Prob­a­bly not too use­ful in itself, but it allows for hook­ing the project into Travis or sim­i­lar CI that auto­mat­i­cal­ly runs npm test.

"private": true

This is pret­ty much what it says on the can. It ensures you don’t acci­den­tal­ly post it onto NPM until you’re good and ready.

Final­ly, run npm install to pull in all the depen­den­cies from NPM. This might take a while, like all installs.

Gruntfile

Cre­ate a file called Gruntfile.js in the root, with the fol­low­ing con­tent:

'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
    // show elapsed time at the end
    require('time-grunt')(grunt);
    // load all grunt tasks
    require('load-grunt-tasks')(grunt);
    grunt.loadTasks('tasks');

    var directoriesConfig = {
        composer: 'vendor',
        composerBin: 'vendor/bin',
        reports: 'logs',
        php: 'app'
    };

    grunt.initConfig({
        directories: directoriesConfig,
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js'
            ]
        },
        jsvalidate: {
            files: [
                'Gruntfile.js'
            ]
        },
        jsonlint: {
            files: [
                '*.json'
            ]
        }

    });

    grunt.registerTask('default', [
        'test'
    ]);

    grunt.registerTask('test', [
        'jsvalidate',
        'jshint',
        'jsonlint'
    ]);
};

There are two tasks reg­is­tered, default and test. There’s also three con­figs defined, adding three more tasks: jshint, jsvalidate and jsonlint. In a full-stack envi­ron­ment, those would also like­ly affect files in the project itself, but in this one they only check the Grunt­file and the JSON con­fig files.

One more thing needs to be added to ensure those tasks run smooth­ly: A .jshintrc file. For fur­ther infor­ma­tion about the var­i­ous lint­ing options, go to JSHint’s web­page, but for now cre­ate a file in the root of the project named .jshintrc, and paste in the fol­low­ing code:

{
    "node": true,
    "browser": true,
    "esnext": true,
    "bitwise": true,
    "camelcase": true,
    "curly": true,
    "eqeqeq": true,
    "immed": true,
    "indent": 4,
    "latedef": true,
    "newcap": true,
    "noarg": true,
    "quotmark": "single",
    "regexp": true,
    "undef": true,
    "unused": true,
    "strict": true,
    "trailing": true,
    "smarttabs": true,
    "predef": [
        "Modernizr",
        "$",
        "jQuery"
    ]
}

If you run grunt from the root now, it will … give you an error. Whoops, Tasks direc­to­ry not found!

There are three sets of tasks/modules loaded in this file:

// A module that prints out the time at the end
require('time-grunt')(grunt);

// Load all grunt tasks from NPM that starts with grunt-
require('load-grunt-tasks')(grunt);

// Load all grunt tasks that are defined locally, currently none
grunt.loadTasks('tasks');

Cre­ate the tasks direc­to­ry: mkdir tasks, and after that run grunt. If there are any issues with either the Grunt­file or the con­figs, fix them.

Composer

As allud­ed to above, the instal­la­tion of Lar­avel is not com­plete, nor do we have all the tools to install the tasks and such. Com­pos­er is a tool, like Bow­er for fron­tend, NPM for Node­JS and Bundler for Ruby, for depen­den­cy man­age­ment for PHP. The file that han­dles what pack­ages to install is called composer.json and is already defined with the depen­den­cies for Lar­avel, but you need to get Com­pos­er. You can install it local­ly or glob­al­ly and in a few dif­fer­ent ways, where I pre­fer keep­ing it local to the project and not check­ing it into ver­sion con­trol. You’ll need to run just two com­mands:

# Download the installer to check that you have all the settings, which in
# turn downloads the latest version of the file into the current directory
curl -sS https://getcomposer.org/installer | php

# Assuming that you didn't need to adjust any settings, run the installation of
# dependencies (you may want to get another coffee ...)
php composer.phar install

I rec­om­mend look­ing over the doc­u­men­ta­tion to see what you can do with Com­pos­er, since this arti­cle will only be touch­ing on it briefly. Like Bundler for Ruby, it cre­ates a lock-file that gives which ver­sions of the pack­ages are installed, to ensure sim­i­lar work­ing envi­ron­ments. Lar­avel by default does not add composer.lock into ver­sion con­trol, which is one of the changes in the .gitignore file.

Diving into the deep end

Make sure you have your ter­mi­nal at the ready and your Gruntfile.js open. We will work through the tasks work­ing off of main­ly those, with the occa­sion­al new file put into tasks.

Commit tasks

PHP-lint

Let’s start with the most basic of tasks: lint­ing! PHP comes equipped with a basic syn­tax check. By run­ning php -l <path/to/file>, it will check if a giv­en file has any syn­tax errors in it, such as stray lack of semi­colon caus­ing a PHP Parse error: syntax error, unexpected end of file. It’s not exact­ly the most advanced of fea­tures, but it’s a good fail­safe before you run any oth­er check­ers.

It’s cor­re­spond­ing grunt task is, not too sur­pris­ing­ly, grunt-phplint by Jacob Gable. Below the con­fig­u­ra­tion for jsonlint add the fol­low­ing.

phplint: {
    options: {
        swapPath: '/tmp'
    },
    all: [
        '<%= directories.php %>/**/*.php'
    ]
}

If you aren’t too well-versed in how to write JavaScript objects, each of the con­figs needs to be sep­a­rat­ed by a com­ma, but there should be no com­ma after the very last con­fig­u­ra­tion. Of course, if you do for­get, you’re like­ly to run the task and encounter a SyntaxError: Unexpected identifier cen­tered on the phplint word.

Anoth­er fun thing error that you might encounter is Warning: Task "phplint" not found. Use --force to continue which is just as straight­for­ward as it sounds, at least in this case: The task is not installed. Let’s rem­e­dy that quick­ly by run­ning npm install --save-dev grunt-phplint. The --save-dev flag means that it’ll write it into the package.json file. If you want to save it as a nor­mal depen­den­cy (rather than devel­op­ment) the flag is instead --save.

Final­ly, run grunt phplint and it’s done.

Let’s look a bit clos­er at the con­fig. Oth­er options can be found in the doc­u­men­ta­tion for the task, but I explicite­ly set the path for where the task should cache files to /tmp. I also (like I will more fur­ther into the guide) used a vari­able defined high­er up in the Gruntfile.js in which files to lint. I like using con­fig­u­ra­tions to be able to change direc­to­ries and such to what­ev­er struc­ture I’m using.

PHP Code Sniffer

Code sniff­ing always sounds so incred­i­bly weird to me. It sounds like a per­son­al prob­lem, though maybe code smell is as well! At any rate, I use the PHP CodeSnif­fer from Squiz Labs, which per their Github page tokenis­es PHP, JavaScript and CSS files and detects vio­la­tions of a defined set of cod­ing stan­dards. (How­ev­er, I haven’t used it for JavaScript or CSS, and unsure on if I will. After all, Sass deals with spit­ting out for­mat­ted CSS, and I trust JSHint to be bet­ter for actu­al code issues with my JavaScript.)

To install the required pack­age and cor­re­spond­ing grunt task run:

php composer.phar require --dev squizlabs/php_codesniffer:1.*
npm install --save-dev grunt-phpcs

The grunt task PHPCS is cre­at­ed by Sascha Gal­ley and is a fair­ly good one-to-one imple­men­ta­tion of the tool. Below phplint, add the fol­low­ing con­fig­u­ra­tion:

phpcs: {
    application: {
        dir: '<%= directories.php %>'
    },
    options: {
        bin: '<%= directories.composerBin %>/phpcs',
        standard: 'PSR2',
        ignore: 'database',
        extensions: 'php'
    }
}

Not that I define where the bina­ry is stored, that it should only sniff PHP-files and that it should ignore the database direc­to­ry. That is in fact the app/database direc­to­ry, because it looks for pat­terns below the direc­to­ry it is run­ning in, but that’s an aside.

Final­ly, the stan­dard is PSR2. It’s my pre­ferred cod­ing style, but it can obvi­ous­ly han­dle any kind of stan­dard. Now, here’s one issue with using the Lar­avel base to test these things out: Despite Lar­avel fol­low­ing PSR-1, the base project doesn’t. Facepalm. Run grunt phpcs to con­firm this, if you want to see what kind of errors it throws.

You now have two choic­es:

  1. Change the stan­dard to PSR1 and just add a name­space to the fol­low­ing files:
    • app/tests/TestCase.php (needs to extend \Illuminate\Foundation\Testing\TestCase)
    • app/tests/ExampleTest.php
    • app/controllers/BaseController.php
    • app/controllers/HomeController.php
    • app/models/User.php (needs to extend \Elo­quent)
  2. Keep the PSR2 and go through the var­i­ous issues. It’s main­ly white­space issues. This com­mit of the test repos­i­to­ry has the PSR2 ver­sion, if you want to just copy the app direc­to­ry from it.

Either choice, once you’ve gone through the changes (or ignored the rest of this task, up to you!), run php composer.phar dump-autoload --optimize, which will recre­ate the autoload files (impor­tant for Lar­avel).

Here’s two bonus tricks:

  • If you are pre­pared to let a pro­gram run through your code (I’m not ful­ly cer­tain I am), there’s the PHP-CS-Fix­er (which even has a grunt task). As much as I like automa­tion, though, I over­all pre­fer to have bet­ter con­trol over that.
  • If you only have tabs rather than spaces expand will do the trick in most *nix sys­tems. Go into the direc­to­ry where you want to do any changes and run the fol­low­ing com­mand (copy all of it):

    find . -name “*.php” |while read line
    do
    expand -it4 $line > $line.new
    mv $line.new $line
    done

If you go with either of those, make sure you have every­thing in ver­sion con­trol or in some oth­er way have back­ups.

PHP-Unit

Because you do write unit tests that you run with every com­mit, right? Right? … Yeah. Me too. At any rate, the Lar­avel project comes pre-equipped with a test unit test, so let’s get the PHP Unit up and run­ning with grunt.

First, remove the phpunit.xml file at the root. You can keep it (the task will read from it), but at least part of what I’m want­i­ng to do here is to gath­er con­fig­u­ra­tions in one for­mat, so for that rea­son, remove it. Now install PHP Unit:

php composer.phar require --dev phpunit/phpunit:3.7.*
npm install --save-dev grunt-phpunit

While we’re wait­ing, what is PHP Unit? It’s one of the pri­ma­ry unit test­ing frame­works for PHP, with the main con­trib­u­tor being Sebas­t­ian Bergmann. It’s a bit too large to cov­er in the scope of this arti­cle, obvi­ous­ly, but there are some very good tuto­ri­als, and the site has good doc­u­men­ta­tion. The task is main­tained by Sascha Gal­ley, and is like the above-men­tioned grunt-phpcs task a good imple­men­ta­tion. All options that one could write in the phpunit.xml con­fig­u­ra­tion file can be han­dled in the Grunt­file.

Below phpcs add the con­fig­u­ra­tion:

phpunit: {
    classes: {
        dir: '<%= directories.php %>/tests'
    },
    options: {
        bin: '<%= directories.composerBin %>/phpunit',
        bootstrap: 'bootstrap/autoload.php',
        staticBackup: false,
        colors: true,
        noGlobalsBackup: false
    }
}

The boot­strap is which file it should load before run­ning the tests, and the dir is obvi­ous­ly where the class­es are stored.

Run grunt phpunit, which should give you a green line with the text OK (1 test, 1 assertion)

Final words

That was the last of the block­ing, pre-com­mit tasks. To get all of them togeth­er, add phplint, phpcs and phpunit to the test task and run grunt. It should pass, if not, fix it before the next sec­tion. Or, you can just copy the files from this com­mit, if you pre­fer that.

Reporting tasks

These tasks are the ones that go through the code and write some kind of report over what it finds. I’ll admit that I’m not as famil­iar with most of them, and they don’t seem to be as com­mon­ly used. The default direc­to­ry for all of these reports is logs/<taskname>, though it can obvi­ous­ly even be out­side of the root. Maybe using grunt-gh-pages or a sim­i­lar task to push the results to a dif­fer­ent repos­i­to­ry entire­ly.

The first thing to do here is to install two helper tasks. One is grunt-contrib-clean, which is a task to delete all files under a spe­cif­ic direc­to­ry, and/or delete a spe­cif­ic direc­to­ry. It’s part of the core plu­g­ins for Grun­tJS, as evi­denced by the contrib part of the name. To install: npm install --save-dev grunt-contrib-clean

The sec­ond one is writ­ten by Sin­dre Sorhus and is called grunt-shell. Not too sur­pris­ing­ly, it allows for run­ning shell com­mands. They’re fair­ly flex­i­ble in what you can do with them, and if what you’re need­ing is a few fair­ly sim­ple and straight­for­ward shell com­mands, it’s quite use­ful. One use I’ve got­ten out of it is run­ning a series of update com­mands; Rather than need­ing to run npm update && bower update && bundle update && pip install -r "requirements.txt". To install: npm install --save-dev grunt-shell

PHP Docs

I like doc­u­men­ta­tion. I don’t like writ­ing doc­u­men­ta­tion, but I do like doc­u­men­ta­tion. Espe­cial­ly in a larg­er team (and a larg­er project), I like the idea of being able to eas­i­ly run one com­mand to gen­er­ate a good, cohe­sive overview of the project and its’ class­es and depen­den­cies. Since this is main­ly an arti­cle on Grunt, I will use more-or-less default options, and leave any tweaks to the tem­plates etc for your own choic­es. For more infor­ma­tion on php­Doc­u­men­tor go here.

To install with Com­pos­er, run php composer.phar require --dev phpdocumentor/phpdocumentor:2.*. As a warn­ing, it’s fair­ly big and has a lot of require­ments. It’s why you need XSL sup­port for your PHP, and Graphviz. The result­ing doc­u­men­ta­tion is pret­ty rad, though.

Now, doc­u­men­ta­tion is one of the things that I find to be least use­ful to keep all of it, so the final php­docs task will start with clean­ing out the old files and then move into gen­er­at­ing the doc­u­men­ta­tion. You’ll want to add logs/phpdocs/phpdoc-* to the .gitignore-file if you’re plan­ning on check­ing in the doc­u­men­ta­tion into any kind of ver­sion con­trol sys­tem.

There is actu­al­ly a task for the doc­u­men­tor. It’s called grunt-phpdocumentor, but the last activ­i­ty was 8 months ago. There are a cou­ple of forks of it, but none that has been pub­lished to NPM, and the lat­est ver­sion there has some debil­i­tat­ing bugs. The solu­tion I’m going to be intro­duc­ing is to copy in the rel­e­vant file (phpdocumentor.js) into the tasks direc­to­ry. You will also need to install lodash when using this task: npm install --save-dev lodash.

Choose which task you want to install from the ones below.

Orig­i­nal task
curl -o tasks/phpdocumentor.js https://raw.github.com/gomoob/grunt-phpdocumentor/master/tasks/phpdocumentor.js
Sascha Galley’s fork adds sup­port for tem­plate
curl -o tasks/phpdocumentor.js https://raw.github.com/SaschaGalley/grunt-phpdocumentor/master/tasks/phpdocumentor.js
My own fork adds sup­port for ignore
curl -o tasks/phpdocumentor.js https://raw.github.com/Melindrea/grunt-phpdocumentor/master/tasks/phpdocumentor.js

Now, for the Grunt­file. Start with copy­ing in the clean­ing con­fig wher­ev­er you feel it’s con­ve­nient:

clean: {
    phpdocumentor: '<%= phpdocumentor.dist.target %>'
},

Then copy in the php­doc­u­men­tor con­fig after phpunit:

phpdocumentor: {
    dist: {
        bin: '<%= directories.composerBin %>/phpdoc.php',
        directory: '<%= directories.php %>',
        target: '<%= directories.reports %>/phpdocs',
        ignore: [
            '<%= directories.php %>/database/*'
        ]
    }
}

The clean­ing task (grunt clean:phpdocumentor) removes the direc­to­ry, the php­doc­u­men­tor task (grunt phpdocumentor) goes through all the files under the app direc­to­ry (except data­base) and reports on what it finds. The files all go into logs/phpdocs, as per that con­fig­u­ra­tion.

Final­ly, let’s add a new task, phpdocs, which first cleans the direc­to­ry and then regen­er­ates the docs. Add the fol­low­ing bit of code under the test task.

grunt.registerTask('phpdocs', [
    'clean:phpdocumentor',
    'phpdocumentor'
]);

Run grunt phpdocs and bask in the glo­ry of shiny doc­u­men­ta­tions! Bask, I tell you!

PHP Loc

Per its’ home­page, phploc is tool for quick­ly mea­sur­ing the size and ana­lyz­ing the struc­ture of a PHP project. It’s one of the tools I’m still try­ing to wrap my head around, so this very first ver­sion of the grunt task is going to uti­lize grunt-shell. The the­o­ry behind this is that we want to have some­thing that steadi­ly reports on the grow­ing com­plex­i­ty (most like­ly con­sumed by a cron job some­where — pos­si­bly ran by a cron job some­where). A real­ly cool exam­ple from the main readme spits out a CSV file with the rows being tied to a repos­i­to­ry. I chose not to use that one in my com­mand, but before get­ting ahead of our­selves, let’s install it: php composer.phar require --dev "phploc/phploc=*".

I am like­ly to even­tu­al­ly write a task for it, but using the shell task works good for the begin­ning use­case, which will just write an XML-file with the results into logs/phploc/<the current datetime>.xml, based on the files in app.

Below the clean con­fig, paste in the shell con­fig:

shell: {
    phploc: {
        command: [
            'mkdir -p <%= directories.reports %>/phploc',
            'php <%= directories.composerBin %>/phploc --log-xml <%= directories.reports %>/phploc/<%= grunt.template.today("isoDateTime") %>.xml <%= directories.php %>'
        ].join('&&')
    }
},

Notice the use of grunt.template.today(), which is what names the file. The sec­ond thing to note is that I’m actu­al­ly join­ing two com­mands. That is because if the direc­to­ry doesn’t exist, the file isn’t saved where it should be.

Final­ly, add anoth­er task below php­docs:

grunt.registerTask('phploc', [
    'shell:phploc'
]);

The main rea­son I’m adding that task is so that I don’t use the shell syn­tax more than nec­es­sary, as I often use the shell task for a quick-and-dirty ver­sion of tasks I intend to actu­al­ly write lat­er.

Run grunt phloc for the very first XML file.

Sensio Labs Security Checker

Though this does not replace doing your own research, it’s at least a good idea to imple­ment some kind of auto­mat­ic check that your com­po­nents aren’t Naughty (boy I wish there was some­thing like this writ­ten for bow­er as well, but I know how much effort goes into it, so it’s not some­thing I can do, at least not at the moment). Alright, now that we’re past that paren­the­sis, do check out their web page, which has at least a touch more details.

Let’s start by installing the require­ment using Com­pos­er: php composer.phar require --dev "sensiolabs/security-checker:1.3.*@dev".

This also uses grunt shell, so add anoth­er tar­get below phploc:

securityChecker: {
    command: 'php <%= directories.composerBin %>/security-checker security:check',
    options: {
        stdout: true
    }
}

Note the option of stdout: true, that means that it will print the out­put to the con­sole. You could obvi­ous­ly save the result, but I at least feel it would be more effort than it’s worth. If I end up find­ing some­thing with a known vul­ner­a­bil­i­ty, I might rework it so that it logs some­thing if it finds it.

Final­ly you’ll want anoth­er task, which I’m going to name vulnerability, since the the­o­ry here is that we might even­tu­al­ly get that fron­tend check that I men­tioned ear­li­er, or oth­er things to check the vul­ner­a­bil­i­ty:

grunt.registerTask('vulnerability', [
    'shell:securityChecker'
]);

PHP Mess Detector & PHP Depend

PHP Mess Detec­tor is a spin-off on PHP Depend. Both projects have (or so I have been assured by Stack­Over­flow) their place in the report­ing. PHPMD also depends on hav­ing PHP Depend installed, which is why the two tasks are writ­ten under the same head­er. I will start with PHP Mess Detec­tor, since it has a grunt plu­g­in writ­ten by Andreas Lappe.

First, install the two depen­den­cies in com­pos­er:

php composer.phar require --dev "phpmd/phpmd:1.4.*"
php composer.phar require --dev "pdepend/pdepend:1.1.*"
PHP Mess Detector

The grunt plu­g­in is on NPM, so to install it, run npm install --save-dev grunt-phpmd. The con­fig­u­ra­tion I’ve decid­ed on is the fol­low­ing, paste that in below the con­fig­u­ra­tion for php­doc­u­men­tor:

phpmd: {
    application: {
        dir: '<%= directories.php %>'
    },
    options: {
        rulesets: 'codesize,unusedcode,naming',
        bin: '<%= directories.composerBin %>/phpmd',
        reportFile: '<%= directories.reports %>/phpmd/<%= grunt.template.today("isoDateTime") %>.xml'
    }
}

I have cho­sen to do one tar­get that checks for all three things it might find prob­lem­at­ic, and then stores the result­ing file in logs/phpmd/<todays datetime>.xml. If you do run the task imme­di­ate­ly, you’ll notice that it com­plains about the direc­to­ry not exist­ing. My solu­tion is to add anoth­er helper plu­g­in, grunt-mkdir. As a note, I could do what I did when I had to cre­ate a direc­to­ry for phploc, which was to use the shell direct­ly. The rea­son I’m not is because I’m not using the shell for phpmd, and there is a task avail­able to install (writ­ten by Ruben Ver­meer­sch). Run npm install --save-dev grunt-mkdir, and then paste in the con­fig­u­ra­tion below the shell con­fig­u­ra­tion:

mkdir: {
    phpmd: {
        options: {
            create: ['<%= directories.reports %>/phpmd']
        },
    }
},

So, since automa­tion is the name of the game, we want to ensure that grunt mkdir:phpmd gets ran before phpmd. To do this, I will reg­is­ter a task phpmdMk that runs first the mkdir:phpmd and then phpmd.

Below the added task vulnerability add the fol­low­ing:

grunt.registerTask('phpmdMk', [
    'mkdir:phpmd',
    'phpmd'
]);

You can now run grunt phpmdMk, and it will act as expect­ed.

As a final note, there is a dis­cus­sion on the php­md plu­g­in on whether it should be a block­ing task. At cur­rent­ly it will return suc­cess no mat­ter what the result of the detec­tor is, but there are dis­cus­sions on adding a con­fig­u­ra­tion to view any errors report­ed as a fail­ure of the task, at which point it could, the­o­ret­i­cal­ly, be put in a pre-com­mit hook as well.

PHP Depend

There is no task for PHP Depend. I may decide to write one for myself once I’ve actu­al­ly fig­ured out what I want to do with all that nice data, but until then I wrote a small func­tion that uses the shell task.

It takes the cur­rent ISO Date­Time and cre­ates a direc­to­ry logs/pdepend/<datetime>. In that direc­to­ry the sum­ma­ry is put (named summary.xml), a JDe­pend chart is cre­at­ed, named chart.svg, and final­ly an overview pyra­mid (pyramid.svg) is cre­at­ed.

Paste in this tar­get below the secu­ri­ty­Check­er tar­get of the shell task:

pdepend: {
    command: function () {
        var now = grunt.template.today("isoDateTime"),
        directory = '<%= directories.reports %>/pdepend/' + now,
        mkdir = 'mkdir -p ' + directory,
        summary = directory + '/summary.xml',
        chart = directory + '/chart.svg',
        pyramid = directory + '/pyramid.svg',
        pdepend;

        pdepend = 'php <%= directories.composerBin %>/pdepend '
        pdepend += '--summary-xml=' + summary + ' ';
        pdepend += '--jdepend-chart=' + chart + ' ';
        pdepend += '--overview-pyramid=' + pyramid + ' ';
        pdepend += '<%= directories.php %>';

        return mkdir + ' && ' + pdepend;
    }
}

This shows anoth­er part of the flex­i­bil­i­ty of the shell task, though this par­tic­u­lar one isn’t very flex­i­ble in the sense of allow­ing dynam­ic calls to it.

Final­ly, add the task grunt pdepend below the phpmdMk task:

grunt.registerTask('pdepend', [
    'shell:pdepend'
]);

Final thoughts

Hope­ful­ly you’ve picked up some new tricks here. There were a cou­ple of more tasks I was inter­est­ed in try­ing to get to work,
but they weren’t coop­er­at­ing with me. Maybe next time, when I’ve reworked the ones that have been defined as grunt shell tar­gets in this arti­cle.

I’d love to hear your thoughts on Twit­ter, and the final ver­sion of the project built here is locat­ed on Github.


For the moment comments are not enabled, but feel free to reach out on Twitter.