Tuesday, July 26, 2011

Quality Assurance on PHP projects - PHPDocumentor

It goes without saying people should document their code so that after a few weeks, months, years they still know what they did initially and why. Besides providing a mental note in the code, it also helps your IDE to figure out what your class is all about, which parameters should be used with the methods and what their return types are.

But theory is not always reality and unfortunately I've come across too many lines of code that were just lines of code, no comments or annotations provided. So, in best cases I could guess the types and parameters, but in many it was too obfuscated. I already talked about usage of a code sniffer like PHP_CodeSniffer in my previous post where you can validate the usage of comments in the code. But forcing developers (using a pre-commit checker) into writing documentation with their code is not really a good thing. Once people are forced in a corner, they tend to figure out ways to escape and your whole idea to deliver better code goes out the window.

As a consultant I face many situations where code is not documented or not documented enough, and the top 5 excuses I always get are:

  • writing documentation is not requested by the customer
  • no time, too many things need to be done between releases
  • too much work, and no one looks at the code anyway
  • why should I?
  • we charge extra for documentation, but the customer doesn't want to pay it

Like I said, these are mere excuses. I'm not here to judge these people, but I have to emphasize these excuses lack a real fundament to be acceptable in a professional environment. Maybe I'm taking to much pride in my "craft" as PHP developer, but don't you want to offer your customers the best you're company can provide?

Let's turn things around and imagine a new developer joins your team. Wouldn't it be great if you had documentation that could create complete documentation on the source code so it's easy to get into the flow of things? And when looking at the code in their favorite IDE whenever they access existing code, wouldn't it be great they could see instantly what parameters are used for a certain method or function with a brief explanation why it's useful? Maybe that's why I consider writing documentation part of writing code, invalidating all 5 excuses mentioned earlier.

Documentation 101

Writing documentation alongside your code is really not that much of work when you think of it. For all classes, you probably have a template that you can use that details your library of code components.

<?php
/**
 * My Awesome Library
 *
 * This library provides additional functionality and resources
 * for our standard framework [fill in your favorite framework]
 *
 * @category My
 * @license Copyright 2011 © My Company, Inc. - All rights reserved
 */

When you actually write your class components, just explain in short what they do and why they've been created.

/**
 * My_Amazing_Component_Class
 *
 * This class provides amazing functionality that extends the framework
 * that's being used by this company.
 *
 * @package My_Amazing
 * @subpackage My_Amazing_Component
 * @version $Id:$
 */
class My_Amazing_Component_Class
{
}

Once you get to the point where you write the method, you only need to provide some additional information about what it does, what the parameters are, the return types and if they throw exceptions or not

/**
 * We need to take two parameters to figure out which is the location
 * that we want to display on the map.
 *
 * @param float $latitude The latitude of the spot
 * @param float $longitude The longitude of the spot
 * @return string The link that displays the spot on a map
 * @throws OutOfBoundsException When values are invalid
 */
public function findTheSpot($latitude, $longitude)
{
}

As you can see, not really difficult to get the documentation in place and when you do it immediately you don't even waste too much time writing it.

Automated API documentation

If you have the documentation in place, there are tools out there that allow to generate API documentation quickly and easily. There are less known but very promising tools like Doxygen, DocBlox, phpdocgen and many more. But the most known is still PHPDocumentor. The tools that were mentioned here all have their pros and cons, but their usage is pretty simple and their outcome is exactly what you need to have good documentation of your source code.

PHPDocumentor is easy to use and requires just a couple of parameters to generate high quality API documentation.

user@server $: phpdoc \
  -o HTML:frames:earthli \
  -ric docs/README.txt \
  -ti "PHPDocumentor Example" \
  -dc MyCompany \
  -dn My_Amazing \
  -d application/models,application/forms,library/My \
  -i *.phtml \
  -t docs/api

Options used here:

  • -o HTML:frames:earthli defines the template to use, in this example we use the framed earthly HTML template
  • -ric docs/README.txt defines the README, INSTALL and CHANGELOG files
  • -ti "PHPDocumentor Example" provides the title
  • -dc MyCompany sets the default category if no category is being provided
  • -dn My_Amazing sets the default package if no package is being provided
  • -d application/models,application/forms,library/My defines the directories that contain the php code you want to document
  • -i *.phtml defines files that should be ignored when rendering
  • -t docs/api defines the location where the API should be generated
Running it on command line will look more or less like this


The result of it is that you get very nice documentation that can be handed out to all developers on your team, third party service providers building on top of your applications or new people on your team.

As you can see, isn't it great to have documentation with your code that requires not much of additional work? Don't forget, it's not just great to create smashing API documents, but it also helps your IDE as it will be able to give your more detail what kind of parameters and methods your classes provide and need.

Sunday, July 17, 2011

Quality Assurance on PHP projects - PHP_CodeSniffer


PHP_CodeSniffer is probably the most convenient tool out there to analyze your source code and to verify it complies to company policies. Although it's debatable why source code should follow strict guidelines, it's only a matter of time before you discover yourself that it pays off to have a code base that appears to be written by one developer.

The first question you have to ask is what standard are you going to implement. There are several standards already packaged with PHP_CodeSniffer, but are they useful within your company? Maybe you want to extend or override some standards with your own implementation. Do remember, the standards supplied with PHP_CodeSniffer have been negotiated over and over by the developers for ages. So if you want to define your own standards, be warned that it can be a long and tedious track before you can agree on a specific standard.

Installation
Installing PHP_CodeSniffer is easy when using the PEAR framework. Make sure you have installed and upgraded the pear libraries that come with your OS. After that all you need to do as root or Administrator is the following.

user@server: $ pear install PHP_CodeSniffer

Or you can go to the download page of PHP_CodeSniffer and download the source package yourself and install it the way you want it. In most cases, the PEAR installation is a more elegant, easy way to install the tool.

Configuration & Execution
PHP_CodeSnifferdoesn't require much configuration, but you have to decide on which coding standard you want to check the code base.

Standards provided by PHP_CodeSniffer are the following:
  • Zend
  • PEAR
  • PHPCS
  • Squiz
  • MySource
user@server: phpcs --standard=PEAR /path/to/php/sources

But as stated, you can also define your own standard and provide the base path of the repository on command line

user@server: phpcs --standard=/path/to/my/standards /path/to/php/sources

There are a lot of extra options provided with this tool, but let me focus here on the more important ones you might find useful in your day-to-day usage of PHP_CodeSniffer.

Ignoring files and paths
If you have a couple of external libraries or test scripts in your PHP projects, you might want to exclude them because they're not really part of your concerns. Wouldn't it be easy to just exclude them from the analysis? The following command will exclude paths you have no interest in.

user@server: phpcs --standard=PEAR --ignore=*/tests/*,*/library/Zend/*

Output options
Sometimes you require a different report than the default report that covers all information. Maybe you require a simple summary, a blame report, source report or a report formatted in XML or CSV for usage in another tool. It's only one option away.

The summary report:
user@server: phpcs --standard=PEAR --report=summary

The blame report (requires project to be checked out from a Subversion server):
user@server: phpcs --standard=PEAR --report=svnblame

More on these report formats can be found on the documentation pages of PHP_CodeSniffer.

Roundup
Running PHP_CodeSniffer on the command line is a very convenient way to investigate if the source code is following the standards everyone has agreed upon. 


You can also set it as a pre-commit hook for your revision control system, but in my experience it has a negative effect on the productivity of the development team. But it never hurts to try it out and see for yourself if it's a positive step or causes frustrations. A full description on how to set it up for Subversion is explained on the SVN pre-commit page of PHP_CodeSniffer.

When you want to ensure everyone on your team follows the standard policies of your department or company, PHP_CodeSniffer is a great tool to identify where developers need to modify their code so it complies.

 

Thursday, July 14, 2011

Quality Assurance on PHP projects - PHPLint






PHP Lint
PHP Lint is probably the easiest way to validate your code on syntax errors, but it's also the most overlooked feature of PHP on command line.

Provides a convenient way to perform only a syntax check on the given PHP code. On success, the text No syntax errors detected in is written to standard output and the shell return code is 0. On failure, the text Errors parsing in addition to the internal parser error message is written to standard output and the shell return code is set to -1.

This option won't find fatal errors (like undefined functions). Use the -f to test for fatal errors too.

Example of detecting failures only
user@machine: $ /usr/bin/php -l /path/to/myfile.php

Example of detecting failures and fatal errors
user@machine: $ /usr/bin/php -lf /path/to/myfile.php

Since this tool also returns numeric return codes, you can use it immediately as a pre-commit hook of your favorite revision control system like Git or Subversion. And with PHP on command line, you can create your own hooks with PHP. 

Example of a Git pre-commit hook taken from Travis Swicegood's article "Don't submit that error" on the PHP Advent pages.
#!/usr/bin/php




// author: Travis Swicegood


$output = array();
$return = 0;
$php = '/usr/bin/php';

exec('git rev-parse --verify HEAD 2> /dev/null', $output, $return);
$against = $return == 0 ? 'HEAD' : '4b825dc642cb6eb9a060e54bf8d69288fbee4904';

exec("git diff-index --cached --name-only {$against}", $output);

$filename_pattern = '/\.php$/';
$exit_status = 0;

foreach ($output as $file) {
    if (!preg_match($filename_pattern, $file)) {
        // don't check files that aren't PHP
        continue;
    }

    $lint_output = array();
    exec("{$php} -lf " . escapeshellarg($file), $lint_output, $return);
    if ($return == 0) {
        continue;
    }
    echo implode("\n", $lint_output), "\n";
    $exit_status = 1;
}

exit($exit_status);

Example of a SVN pre-commit hook I use myself
#!/usr/bin/php




/**
 * Script that processes your code before it's committed to a Subversion
 * repository.
 */

/**
 * @var string Contains the path to the repository
 */
$repo = $argv[1];

/**
 * @var string Contains the ID of the transaction
 */
$transaction = $argv[2];

// Define the command line tools here
define('PHP', '/usr/bin/php');
define('SVNLOOK', '/usr/bin/svnlook');
define('GREP', '/usr/bin/grep');
define('AWK', '/usr/bin/awk');

/**
 * Checks for commit messages and sees if they are present. More fine
 * grained validation of commit messages can be provided here. If a
 * correct message was given, the function returns false. In all other
 * cases it will return an error message.
 *
 * @param string $transaction The ID of the transaction
 * @param string $repo The path to the repository
 * @return bool|string
 */
function checkCommitMessage($transaction, $repo)
{
    $message = false;
    $log = SVNLOOK . " log -t {$transaction} {$repo}";
    $commitMessage = null;
    exec($log, $commitMessage);
    if ('' === (string) $commitMessage[0]) {
        $message = 'Required commit message is missing' . PHP_EOL;
    }
    return $message;
}

/**
 * Checks the syntax of PHP Code with PHP Lint. If no errors where
 * detected, it will return false. In all other cases, it will
 * return the error messages
 *
 * @param string $transaction The ID of the transaction
 * @param string $repo The path to the repository
 * @return bool|string
 */
function checkSyntax($transaction, $repo)
{
    $messages = array ();
    $result = null;
    $return = false;
    $changed = SVNLOOK . " changed -t {$transaction} {$repo}|"
             . GREP . " \"^[UA]\"|"
             . GREP . " \"\\.php\$\"|"
             . AWK . " '{print \$2}'";
    exec ($changed, $result);
    foreach ($result as $file) {
       $phpLint = array ();
       $lint = SVNLOOK . " cat -t {$transaction} {$repo} {$file}|"
             . PHP . " -l 2>&1";
       $error = exec ($lint);
       if ('No syntax errors detected in -' === $error) continue;
       $messages[] = "{$file} contains PHP syntax errors";
    }
    if (!empty ($messages)) {
        $return = implode(PHP_EOL, $messages);
    }
    return $return;
}

/**
 * Checks return values of these pre-commit functions and returns
 * the return code: 0 for no messages and 1 in any other case.
 *
 * @param bool|string The message output of pre-commit functions
 * @return int The exit code
 */
function getExitCodes($message)
{
    if (false !== $message) {
        file_put_contents('php://stderr', $message);
        return 1;
    }
    return 0;
}

$exitCodes = array ();
$exitCodes[] = getExitCodes(checkCommitMessage($transaction, $repo));
$exitCodes[] = getExitCodes(checkSyntax($transaction, $repo));

if (in_array(1, $exitCodes)) {
    exit(1);
}
exit(0);

Once you have a pre-commit hook in place, you don't have to worry that you commit code contains errors and might cause a failure for other developers in your team. The revision control system will notify you about any failures.

Quality Assurance on PHP projects - Introduction

Quality Assurance has become an increasing important part of web application development, especially with PHP applications. When I look back in history it was common people just deployed web apps and tested it with a browser on the production server. We all know this process couldn't cover all possible user interactions and many bugs were reported back by end users the days after the deployment of a new release.

Luckily there are a lot of tools available that allows you to increase quality of these web applications, and the best part is they are all based on PHP! Let's have a quick look at what tools are interesting to start improving quality assurance on your PHP projects.

These series will cover most of the tools that will allow you to ensure your PHP projects will have an increased quality and your developers become more confident in their work.

If you want to know more about increased quality and better code, I can recommend reading the PHP QA Book, written by Stefan Priebsch and Sebastian Bergmann. It's official title is "Real-World Solutions for Developing High-Quality PHP Frameworks and Applications" published by Wrox Press (ISBN 978-0-470-87249-9).


In these series I do want to dig a little deeper in tools you might have heard of or concepts you agree are important, but are too high-level to comprehend. I also would like to open up a discussion to see if these tools are giving you the expected results or not.
Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License.