Static analysis with PHPSA: PHP Smart Analyzer
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
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
.
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.
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.
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!