Static analysis with PHPSA: PHP Smart Analyzer

Claudio Ribeiro
Share

One requirement that never changes whether you are working on your projects alone or in a team, on small projects or big, is Code Quality. The bigger the project and the team, the harder it gets to maintain it.

A good way of slowing this increase in difficulty down is to use static analysis tools. Static analysis is the process of analyzing software without actually executing the program – a sort of automatic code review. Static analysis tools will detect common errors, enforce coding standards, and even clean up code blocks. The days of php -l filename are not over, but we now have a number of great tools that go the extra mile in helping us create and maintain high quality code.

Speaking of php -l filename, the good old PHP lint, it’s what will execute a syntax analysis on the target file and output any errors it finds. I used to have this little piece of code that I used on and off to send emails with PHP. It is a good starting point for our analysis.

<?php

class Email{

    //Constructor
    function Email( $subject, $message, $senderName, $senderEmail, $toList, $ccList=0, $bccList=0, $replyTo=0 ){

        $this->sender = $senderName . " <$senderEmail>";
        $this->replyTo = $replyTo;
        $this->subject = $subject;
        $this->message = $message;

        // Set the To recipients
        if( is_array($toList)){
            $this->to = join( $toList, "," );
        }else{
            $this->to = $toList;
        }

        // Set the cc list
        if( is_array($ccList) && sizeof($ccList)){
            $this->cc = join( $ccList, "," );
        }else{
            $this->cc = $ccList;
        }

        // Set the bcc list
        if( is_array($bccList) && sizeof($bccList)){
            $this->bcc = join( $bccList, "," );
        }else{
            $this->bcc = $bccList;
        }
    }

    function sendMail(){

        //create the headers for PHP mail() function
        $this->headers = "From: " . $this->sender . "\n";
        if( $this->replyTo ){
            $this->headers .= "Reply-To: " . $this->replyTo . "\n";
        }
        if( $this->cc ){
            $this->headers .= "Cc: " . $this->cc . "\n";
        }
        if( $this->bcc ){
            $this->headers .= "Bcc: " . $this->bcc . "\n";
        }

        print "To: " . $this->to ."<br>Subject: " . $this->subject . "<br>Message: " . $this->message . "<br>Headers: " . $this->headers;
        return mail( $this->to, $this->subject, $this->message, $this->headers );
    }
}

As you can see, this is a simple email sending class. If we run our PHP lint
on this code, we will see that everything is good.

php -l Email.php

The result is the following:

No syntax errors detected in Email.php

In 2016, this result is not enough, because we also need to consider code quality and programming standards.

Enter PHP Smart Analyzer

Lost in the static

PHPSA is a static analysis tool for PHP.

PHPSA can be installed as a .phar, or through Composer, like so:

composer require ovr/phpsa

This will create a command line utility that will be symlinked to the vendor/bin folder of our project.

Using PHPSA

After the installation is done, we can run ./vendor/bin/phpsa.

PHPSA options

The result we get after the above executes is the same result as running the list command. The help command will list the instructions for running help on PHPSA. The check command will execute the static analysis on a designated file or folder.

Because we ran the PHP lint earlier, it is expected that PHPSA will not find any syntax errors on our code. But what happens if we insert an error on purpose? Will PHPSA be able to find it?

Let’s make a slight change in our Email class.

<?php

class Email{

    //Constructor
    function Email( $subject, $message, $senderName, $senderEmail, $toList, $ccList=0, $bccList=0, $replyTo=0 ){

        $this->sender = $senderName . " <$senderEmail>";
        $this->replyTo = $replyTo;
        $this->subject = $subject;
        $this->message = $message;

        // Set the To recipients
        if( is_array($toList)){
            $this->to = join( $toList, "," );
        }else{
            $this->to = $toList;
        }

        // Set the cc list
        if( is_array($ccList) && sizeof($ccList)){
            $this->cc = join( $ccList, "," )
        }else{
            $this->cc = $ccList;
        }

        // Set the bcc list
        if( is_array($bccList) && sizeof($bccList)){
            $this->bcc = join( $bccList, "," );
        }else{
            $this->bcc = $bccList;
        }
    }

    function sendMail(){
        //create the headers for PHP mail() function
        $this->headers = "From: " . $this->sender . "\n";
        if( $this->replyTo ){
            $this->headers .= "Reply-To: " . $this->replyTo . "\n";
        }
        if( $this->cc ){
            $this->headers .= "Cc: " . $this->cc . "\n";
        }
        if( $this->bcc ){
            $this->headers .= "Bcc: " . $this->bcc . "\n";
        }

        print "To: " . $this->to ."<br>Subject: " . $this->subject . "<br>Message: " . $this->message . "<br>Headers: " . $this->headers;

        return mail( $this->to, $this->subject, $this->message, $this->headers );

    }

}

This time around we have a clear syntax error in our code. Let’s run PHPSA and check the results.

PHPSA syntax errors

As we can see, PHPSA is quick to detect a syntax error. But none of this is actually new, our simple PHP lint can detect this error, too. So let’s correct it and check what else PHPSA has in store for us.

PHPSA static analysis

A lot of things to look at now!

Notice:  Missing docblock for Email() method in src/Email.php on 6 [missing-docblock]

Notice:  join() is an alias of function. Use implode(...). in src/Email.php on 15 [fcall.alias]

Notice:  sizeof() is an alias of function. Use count(...). in src/Email.php on 21 [fcall.alias]

Notice:  Property sender does not exist in Email scope in src/Email.php on 38 [undefined-property]

PHPSA warns us about a lot of different things, many more than the ones listed above. From functions that are just aliases of others, undefined properties and missing docblocks, PHPSA does a great job suggesting the use of better coding principles.

Let’s fix our code to correct all the above.

<?php

/**
 * Simple email sender class
 *
 */
class Email
{

    /**
     * The email headers
     */
    var $headers;
    /**
     * The email sender
     */
    var $sender;
    /**
     * The email recipients
     */
    var $to;
    /**
     * Reply To
     */
    var $replyTo;
    /**
     * Email cc list
     */
    var $cc;
    /**
     * Email bcc list
     */
    var $bcc;
    /**
     * Email content
     */
    var $message;
    /**
     * The subject of the email
     */
    var $subject;

    /**
     * This is the constructor for the Email class
     */
    function Email(
        $subject,
        $message,
        $senderName,
        $senderEmail,
        $toList,
        $ccList = 0,
        $bccList = 0,
        $replyTo = 0
    ) {

        $this->sender = $senderName . " <$senderEmail>";
        $this->replyTo = $replyTo;
        $this->subject = $subject;
        $this->message = $message;

        // Set the To recipients
        if (is_array($toList)) {
            $this->to = implode($toList, ",");
        } else {
            $this->to = $toList;
        }

        // Set the cc list
        if (is_array($ccList) && count($ccList)) {
            $this->cc = implode($ccList, ",");
        } else {
            $this->cc = $ccList;
        }

        // Set the bcc list
        if (is_array($bccList) && count($bccList)) {
            $this->bcc = implode($bccList, ",");
        } else {
            $this->bcc = $bccList;
        }
    }

    /**
     * The function that actually sends the email
     *
     * @return boolean Returns TRUE if the mail was successfully accepted for delivery, FALSE otherwise.
     */
    function sendMail()
    {

        //create the headers for PHP mail() function
        $this->headers = "From: " . $this->sender . "\n";
        if ($this->replyTo) {
            $this->headers .= "Reply-To: " . $this->replyTo . "\n";
        }
        if ($this->cc) {
            $this->headers .= "Cc: " . $this->cc . "\n";
        }
        if ($this->bcc) {
            $this->headers .= "Bcc: " . $this->bcc . "\n";
        }

        print "To: " . $this->to . "<br>Subject: " . $this->subject . "<br>Message: " . $this->message . "<br>Headers: " . $this->headers;

        return mail($this->to, $this->subject, $this->message, $this->headers);
    }
}

Not a lot of changes but enough for us to understand how useful a tool like PHPSA can be. We went from undocumented, kind of sloppy code, to fully documented and clear code. It is now easier to understand what every property and function in our code does. Running PHPSA now, we will not see any errors or warnings, which means we’ve just added another layer of quality to our code.

Conclusion

PHPSA is open source, which means that we can actually follow its development, request functionality and contribute for it, and since it is a focused tool, PHPSA is fast and lightweight. At this point it is still in early alpha stage, which means that it can behave strangely sometimes, mainly giving different results on different operating systems. Also, a lot of functionality is still missing.

Static analysis is a valuable tool if we want to enforce quality standards in our code bases. It becomes even more valuable when working in a team, as it forces everybody to use the same standards. Even though it is still a little behind some other tools like Code Sniffer or Mess Detector, PHPSA is a very useful tool that shows a lot of promise. Since one of the better ways to cover a wider range of errors is to combine various analysis tools, consider using PHPSA in your QA stack. Be sure to take it for a spin, and maybe contribute to the project on github where it lists a variety of todos and planned features.

Have you tried PHPSA yet? Let us know how you feel it compares to the rest!