PHP
Article

Static analysis with PHPSA: PHP Smart Analyzer

By Claudio Ribeiro

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!

  • Tiago Peralta

    Great article my friend, just an “add-on” , instead of adding it to composer/per project, why not create a phar or download it of it exists, and add it to let’s say /usr/local/bin as an executable, so every other project can use it in a more easy way?

    Anyway, great one, and will certainly check it .
    Keep it up!

    • Claudio Ribeiro

      Thanks man!
      Definitely, if you want to use for all of your projects it doesn’t make any sense to install it per project. I think you can actually do it with composer by doing a global require instead of just a require.

      • Rasmus Schultz

        A download or global install are very convenient, but doesn’t guarantee that other project contributors are running the same version, or running the tool at all. A local install with “require-dev”, and a Composer script to launch any QA tools with the correct settings, is a more reliable approach.

        On another note, I wish that people would stop creating more static analysis tools that solve the same problems over and over again. Instead, I’d like to see a tool that aggregates analysis results from CS, MD, phan, etc. and perhaps map the results against “git blame”, and output the results in a single, consistent report format. I don’t want to add a fourth analysis tool to the stack just for that.

        • Claudio Ribeiro

          Hi rasmus, I guess we can’t stop people from writing whatever tool they want to write. But we can try to analyze and check the tools that are out there and that are the best for whatever we are trying to accomplish. And I’m definitely with you on an aggregation tool; maybe it’s a good project to tackle in the future.

  • wpillar

    Why are you using PHP 4 style constructors in this tutorial? They were deprecated in PHP 5 and removed in PHP 7.

    • Claudio Ribeiro

      The idea was to have somewhat sub optimal code and have the tool perform its check. It is very well spotted and probably a nice Issue to open on the official PHPSA github page.

      • Edson Medina

        Also, the tool didn’t mention:
        * the lack of function visibility declaration
        * using `var` for properties (php4)
        * …and others that the likes of phpmd would report

        • Claudio Ribeiro

          Yes, definitely! It is still a tool that is evolving. If you check the Issues page on their github account, you can see that some of those situations were already reported and are waiting to be tackled.

  • Claudio Ribeiro

    I actually can’t figure that out. Maybe it’s just a matter of having a clear standard, where you use either one or the other, and the developer of PHPSA decided to use implode. Other than that, I can’t find a clear reason.

    • http://dmtry.me/ Дмитрий Пацура

      I dont think that join alias is needed, maybe in the feature someone of Core PHP contributors will suggest an RFC to remove unneeded aliases,

      btw, in near feature, this analyzer can be disabled by own configuration for the project

    • http://waryway.com/ Kyle Wiering

      No idea either. Personally I tend towards implode as I work on a code base that uses ‘join’ as part of a sql object builder.

  • http://dmtry.me/ Дмитрий Пацура

    Wow, rly cool article about my project, heh :+1:
    The main problem why it’s an alpha version

    Because when I started to write it, I was think that it’s a static analyzer, but It’s not
    There is a Compiler component, it’s like an simple own runtime
    That can compile any expression or statement with result

    I am going to start streaming development to get motivation for next cool features related to this project

    P.S Any developer/contribution will be rly helpfull, I am available in skype, discord, mail to help
    new developers with near feature

    Thank you for this article bro :smiley_cat:

    • Claudio Ribeiro

      Really glad you stopped by and glad you enjoyed the article. Maybe in the future, when time is not so much of an issue I will try and contribute.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.