Get Started with PHP Static Code Analysis

PHP Static Analysis

Imagine a world where your IDE or code editor detected problems before you even ran the code.

Imagine if whole categories of errors could be automatically identified and easily eliminated from your codebase.

Imagine better code completion, more stable systems, and less time spent debugging.

Imagine if your computer could not only find bugs but actually fix some of those bugs for you.

Sound good?

These are the promises of strongly-typed programming languages and of static code analysis tools for weakly-typed programming languages like JavaScript and PHP. These are ideas that are taking off in the PHP and JavaScript worlds.

Strongly-typed code is becoming increasingly popular with PHP developers. More type-related features are being added to the language in each new version and installs of PHP static code analysis tools are increasing fast.

Chart showing growth of PHPStan installs. Data comes from packagist.org.

In addition, according to the State of JavaScript survey, TypeScript usage grew from 25% to 78% between 2016 and 2020.

Man, I sure do love TypeScript. Authoring TS, bug detection, auto-completion, refactoring, and general developer productivity are all amazing with TypeScript. Wes Bos – Front-end developer and educator

So what’s all the fuss about? What are the pros and cons of strong typing and static analysis? And how can you tell if this is right for you?

Table of Contents

  1. Taking Flight With Static Code Analysis
  2. What is Static Code Analysis?
  3. Static Code Analysis Tools for PHP
  4. Let’s Get Started With PHPStan
  5. Running PHPStan on the Command Line
  6. Next Steps
  7. Leveling Up
  8. Integrating With Your IDE
  9. Useful Resources
  10. Final Notes

Taking Flight With Static Code Analysis

Rewind 20 years. I’ve just left university (I’m that old) with my Computer Science degree, and the first step of my career is working in safety-critical software development in the aerospace industry. My code literally “took flight.”

The stakes are high in aerospace and other safety- and mission-critical fields. Lives are at risk. A bug doesn’t just mean a website goes down, or you can’t sell shoes. It means a train crashes, a bank ceases trading, a medical device malfunctions during a surgical procedure, or an airplane loses control.

So code is subject to rigorous analysis, including multiple levels of testing and mathematical proofs that the code meets the specification correctly.

Eliminating simple errors from source code early on is key in these industries because time running code on a simulator, in the air, or in the operating theater is hard to come by. You don’t just refresh a browser and check a console. It’s way harder and way more expensive.

Static code analysis is a key part of the process of finding problems early. My job back then used not just the strongly-typed Ada language, but a restricted subset of Ada called Spark which has provable properties and its own static analysis tools.

This is so effective that my team once discovered a bug in an embedded system’s CPU. We’d proven that the code should do something, but it didn’t. We kept digging until we found that the processor was at fault!

Let’s be clear: this is an extreme end of the software development spectrum. But the point is that these proven techniques make software reliable. And they can make software development faster and cheaper by reducing errors, debugging, and change cycles.

There is now compelling evidence that development methods that focus on bug prevention rather than bug detection can both raise quality and save time and money. A recent, large avionics project reported a four-fold productivity and 10-fold quality improvement by adopting such methods.” Peter Amey, “Correctness By Construction: Better Can Also Be Cheaper

Static analysis is not new. There are spheres of software development where it has been keeping you safe for decades.

Web development is a bit more “down to earth” than aerospace software development though. So what is static analysis and how can it help us in our jobs?

What is Static Code Analysis?

Static analysis runs checks on your source code before it is compiled or executed. It doesn’t check that your code does what it’s intended to do—that’s what unit testing and integration testing are for. But static analysis can perform many useful checks on your code without needing to run it.

You’d be surprised at how helpful it can be. Static analysis can eliminate simple but easily-made errors and enforce coding standards and styles across teams. Some tools can even detect security vulnerabilities in your code.

Once you start using proper type checking with @psalmphp/@phpstan, there is no way back. You know that right? Nuno Maduro – Laravel core contributor

Static analysis tools scan your code to gain an understanding of it, and then use that understanding to check what might be wrong.

Some simple examples of what static analysis can tell you:

  • You’re using an integer as a boolean
  • You’re passing an array to a function that expects a string
  • This function is never called
  • This condition always returns true
  • This chunk of code is unreachable and will never be executed
  • This variable might sometimes be empty or of a different type that you don’t check for

Here’s a great example of PHPStan—one of the tools we will look at—being useful. This is some PHP code I was working on recently that takes a string of JSON and turns it into an associative array:

$json = file_get_contents( $file_path );
$json = json_decode( $json, true );

Here’s what PHPStan told me about it:

Parameter #1 $json of function json_decode expects string, string|false given.

A helpful reminder that file_get_contents() returns false on failure, and we should catch that and handle it appropriately.

These probably seem like things you can pick up in code review. But static analysis can operate across an entire codebase and its automated nature makes the checks fast and reliable.

Static analysis tools can be run manually, or automatically by your editor/IDE, on Git hooks, or as part of a deployment process.

I added static analysis to a small package recently. Within minutes I identified an edge-case bug in the logic of a condition, submitted a PR, and got it fixed! There is real value to be had in these tools.

Static analysis is one of the best things to happen to PHP since Composer. We owe a lot to tools like @phpstan and @psalmphp for improving the quality of PHP code. James Titcumb

Note: In order to update your PHP code to implement the static code analysis tools we’ll be discussing, you need to be running PHP 7.1 or later. If you’re not running this, well, you should be as it’s not even getting security updates now.

Leaning Into Types

Static analysis leans heavily on a language’s type system. The more information you can provide about what your code is doing, the better results you will get. And declaring types is the main way of adding more information.

For example, let’s say you have a function that gets a list of featured posts. You could write that function like this:

function hfm_get_featured_posts( $args )
{
  // Function code
}

However, adding the expected parameter and return type declarations:

function hfm_get_featured_posts( array $args ) : array
{
  // Function code
}

…or adding a PHPDoc comment declaring the types of the function inputs and outputs gives the static analyzer more information about the function, and makes the analysis more useful.

/**
 * Get the featured posts.
 *
 * @param array<string, mixed> $args  Arguments to be passed to the query.
 * @return array<WP_Post>
 */
function hfm_get_featured_posts( $args )
{
  // Function code
}

I think this is why static analysis hasn’t previously been popular in web development. PHP and JavaScript both take care of types for you to some extent, converting variables between types automatically when required. This is one of the things that has made it easy to get into programming for the web. You don’t need to learn about types and type casting/juggling. The computer handles it for you.

Adding in all this syntactic “clutter” and the need to learn a load of computer-sciencey stuff has kinda been frowned upon. It gets in the way of shipping code. “Move fast and break things” has been the mantra.

But we’ve seen that there might be some substantial benefits to be had from static analysis, and the web development world is embracing the tools and techniques more and more. And the static analysis dream is a win-win: moving faster and fixing things!

The aim of this article is to give you a taste of what can be done and what is involved in getting the tools up and running, preparing you to find out more and do some experimentation by yourself.

Static Code Analysis Tools for PHP

I know you’re keen to get started, but let’s quickly survey the tooling landscape.

To start with, code-style checking tools like PHPCS and PHPMD could be classed as static analysis tools, as they do some analysis beyond just code style checks. But I’d like to take a look at tools that are designed to deeply analyze your code, including performing type checks.

PHP has four popular static analysis tools in this category:

  • PHPStan: A free and open source command-line tool released by Ondřej Mirtes in 2016. It is actively developed, has over 10,000 GitHub stars, and is supported by some significant-looking sponsorship and the new PHPStan Pro product. There are editor/IDE extensions to bring its analysis into the editor in sort-of real-time.

  • Psalm: Another free and open source command-line tool. It is actively maintained by the Vimeo team so it has good commercial backing, and has over 5,000 GitHub stars. It also has editor/IDE extensions to bring its analysis into the editor.

  • Phan: This is another free and open source command-line tool. Unlike PHPStan and Psalm, there are fewer resources to help you learn how to use this tool. It appears to be a completely open-source initiative with Rasmus Lerdorf (creator of PHP) listed as one of the contributors. This implies it has the backing of Etsy, where he works. It requires the php-ast PHP extension to be installed, and, like the other tools, has editor/IDE integrations. It has over 5,000 GitHub stars but doesn’t seem as popular or as well documented as other entries on this list.

  • PHPStorm: JetBrains’s advanced PHP IDE isn’t free, but it has its own static analysis built-in as well as lots of other advanced developer features.

You can find discussions about the pros and cons of some of these tools if you look around the internet, such as this Reddit discussion. In my experience of getting started with these tools, PHPStorm is a great starting point if you use it as your IDE. You may already be benefitting from static analysis without knowing it! But beyond that, PHPStan was the easiest to set up and get up and running. Psalm was a bit harder and I found the initial output less useful, but I sense that it would be more valuable once you got over the initial hurdles.

You can totally combine these tools too. Maybe get started with PHPStan, fix up the errors it gives you, and then run Psalm on your codebase to see if it picks up anything else.

Why not both?

Ultimately, you and your dev team will need to decide what is your standard, and be sure to be consistent with it.

You can also do static analysis in JavaScript using a variety of tools. You don’t have to embrace TypeScript to get started. There are much simpler steps you can take. We’ll take a very brief look at this later on.

Let’s Get Started With PHPStan

I can’t give you a guided tour of all the available tools, but let’s get started with PHPStan. Many of the concepts are common to the tools, and setup processes are pretty similar across tools (except PHPStorm, which is all built-in).

The general overview is:

  1. Install the analysis tool (and extensions) with Composer into your project
  2. Create a simple configuration file
  3. Run a test from the command line
  4. Add an extension into the editor/IDE and configure it

Install PHPStan

Let’s install PHPStan into a project of ours. It could be a theme or a plugin we are developing, or a package intended to be included in other projects. We will do all of this on our local development setup and we’re pretty much following the excellent PHPStan documentation.

I’m assuming you are familiar with Composer. If not, you’ll need to learn a bit about that first and get it installed, as this isn’t a Composer tutorial. I’ve also assumed a little knowledge of how to use a terminal/command line.

If you aren’t already using Composer in your project you’ll need to create a composer.json file in the top-level directory of your project (the base directory of your own code). This is usually the base directory of your plugin or theme, not the top-level WordPress directory.

If you are using Composer to manage WordPress and plugins, then you can install the static analyzer in the root of your WordPress project, but you’ll want to use the config file to tell it to only analyze your own code. If you’re doing this, then I’m assuming you’re experienced enough to figure this out.

Once Composer is initialized, PHPStan is installed by running the following command, also in the top-level directory of your project:

$ composer require --dev phpstan/phpstan

We actually need some other packages too, but let’s skip to configuring and running PHPStan to see why.

Configure PHPStan

Next, we need to create a simple configuration file. This will also go in the top-level directory of your theme/plugin code. This file is in neon format, which is very similar to YAML if you’ve used that.

We’ll add the following to a file called phpstan.neon:

parameters:
    level: 0
    paths:
        - src

Indentation is significant in this file, and you can use spaces or tabs as long as you are consistent.

The level here is a number between 0 and 9 and determines how strict the checks are, with 9 being the most strict. If you are new to static analysis or adding PHPStan to an existing project, the recommendation is to start at level 0 and work your way up, fixing things as you go. You can dive in at a higher level if you like, but let’s start with some simpler checks.

The paths are the places that PHPStan will look for PHP files to analyze. If you don’t state paths then all PHP files in the current directory and below will be analyzed. This usually means the whole project. Our example config file would specify that the src directory should be examined.

You can also choose to analyze specific files:

    paths:
        - src 
        - hfm-featured-posts.php

And you can use excludePaths to prevent code from being analyzed.

    excludePaths:
        - vendor

You should not analyze other people’s code, so it may be necessary to exclude your vendor directory and others, depending on how your project’s code is organized.

If you’re wondering how PHPStan knows about functions, variables, and classes defined in third-party packages, I’m coming to that.

With this file saved as phpstan.neon we should be ready to run our analysis.

Running PHPStan on the Command Line

The next step is trying PHPStan by running it on the command line. Do this from the root of your project, the same place you put the phpstan.neon file.

$ vendor/bin/phpstan

If things worked right then it’s possible you’re thinking “WOAH! That’s a lot of errors!” If you don’t see this, then congratulations. You can skip the next bit. But I’m assuming that you see lots of errors.

PHPStan output with lots of function not found messages.

The first thing you will probably see is a whole load of Function <some_function> not found messages.

Remember I said not to analyze other people’s code? This is what’s happening here. You’re probably calling some function or using some class from WordPress. And PHPStan doesn’t know about WordPress functions. Yet.

So we need to give PHPStan the information it needs. It doesn’t need the whole WordPress codebase, just the names and type signatures for the variables/classes/functions/methods that you are using.

Fortunately, there are PHPStan extensions for many popular frameworks and PHP packages, including WordPress, WP-CLI, WooCommerce, and so on. So to fix this we can include the PHPStan WordPress package, which has simple instructions in its GitHub README.

The PHPStan WordPress package provides PHPStan with other assistance in understanding WordPress code too, and the README has some other tips for developing clean, testable code. There are other WordPress stubs files available if you want an alternative to PHPStan WordPress.

Once that’s installed, you can re-run PHPStan from the command line and…hopefully…you will start to see more useful messages.

For anything you are using that doesn’t have an existing package, it’s relatively simple to create your own stub files to fill in the gaps.

Next Steps

It’s entirely possible that you already write awesome code and you passed level 0 with no errors. In which case, you can skip this bit and go to level 1.

If your code is not-quite-awesome-yet then you’ll be faced with some errors. If there are so many errors that it’s overwhelming, you can run the analysis on a specific file to get focused in:

$ vendor/bin/phpstan src/wp-content/plugins/hfm-simple-tables.php

Alternatively, there are instructions later on for adding PHPStan to your editor/IDE. But let’s stay on the command line for now.

It’s likely that some of these errors will make sense, and some may not and you’ll need to go Googling. Let’s look at some examples.

I have one WordPress plugin that I ran PHPStan on and it survived level 0 and level 1 okay, but started giving errors at level 2. Let’s look at those, and then we’ll look at how to advance through the levels in the next section.

------ --------------------------------------------------------------------------
  Line   class-sync-pinboard-core.php
 ------ --------------------------------------------------------------------------
  40     Cannot access property $update_time on array.
  89     Comparison operation ">" between int|WP_Error and 0 results in an error.
 ------ --------------------------------------------------------------------------

Now, these errors are—I hope—relatively easy to understand if you have a solid understanding of PHP. But they do need you to reference the code for them to make proper sense.

The first looks like I’m trying to use an array as an object. The second is saying, “you’re doing a numeric operation (>) with a variable that could be a WP_Error object.”

If I trace the code for the first error it ends up at an API call which does this:

$bodyText = (string) $response->getBody();
$bodyData = json_decode($bodyText);

return $bodyData;

This seems fine, as json_decode probably returns an object but the caller thinks we’re getting an array. Looking deeper, it turns out that this API method has a docblock which says it returns an array or null value, which is incorrect.

/**
 * @return array|null
 */ 

I can fix this by changing the docblock to @return mixed|null, but the important thing is that we’re already asking interesting questions about our code. I might revisit the PHP documentation for json_decode to check exactly what it can return and if there’s some other condition that I need to check for to make my code more robust.

Taking it further, you could even add extra checks in this API method to ensure that it returns something more specific than mixed|null, such as stdClass|null. You’ll build more understanding of how to do this as you gain more experience with the type system and static analysis.

Let’s look at the second error, which was:

Comparison operation ">" between int|WP_Error and 0 results in an error.

The code which caused this error does this:

$result = wp_insert_post( $post_data );

if ( $result > 0 ) {
  ...
}

Again this gets us asking interesting questions about our code. What does wp_insert_post() return? Looks like it can be a WP_Error object, and I’m not checking for that case.

We can fix that by adding:

// Bail if we can't insert a post.
if ( $result instanceof \WP_Error ) {
    // Useful error handling here… then...
    return;
}

With these errors fixed we get green!

PHPStan output with no errors.

Great! We can progress to the next level.

Leveling Up

So you passed level 0. Hooray! So let’s move up and see what’s next. The quick and easy way on the command line is to use the -l <level> option to advance:

$ vendor/bin/phpstan src/wp-content/plugins/hfm-simple-tables.php -l 1

This level setting won’t be saved or recognized in future when we add PHPStan to your editor/IDE. Instead, you can edit the phpstan.neon file and increase the level there and then run the command without the -l <level> option.

Details of what rules apply at which rule levels are on the PHPStan website.

Hopefully, you’re on your way now, and can advance up the levels, figuring out what the messages mean, and fixing (or ignoring) the problems.

If something has you stumped and you want to move past it then you can use several methods to tell PHPStan to ignore errors:

/** @phpstan-ignore-next-line */
  • You can ignore whole types of errors across your codebase or in a single file by adding settings in the config file.

  • Or you can make use of the baseline feature to declare static analysis bankruptcy, ignore all the current errors and only report new ones going forward.

As you work through the increasing strictness of the levels, you will start to see patterns in the type of code that fails and what kind of fixes you need to make. Tedious? Yes! But an extremely valuable learning experience. And with time you’ll learn to write the more specific, more robust code that the analyzer expects.

A final note on leveling up: PHPStan level 6 requires you to add type declarations (sometimes called type hints) either in the docblocks, or as actual PHP type declarations. I won’t discuss the pros and cons of these two methods here, but I note this as level 6 is quite noisy in its output and it’s hard work to get things fixed up if your project isn’t already using docblocks and types. You may want to stop for a while at level 5 as the next step could be significant work.

We’ve looked here at PHPStan specifically. But this should give you a good grounding and empower you to go and try out some of the other tools mentioned here too. A lot of the concepts and practices will carry over into other similar tools, and the whole process of analysis doesn’t harm your code; though the process of fixing up any issues can. I recommend working on a separate branch of your codebase just in case and doing whatever testing you normally do.

That’s it, you should have all you need to get started.

Integrating With Your IDE

Okay, I know, I know. You don’t want to pop up a terminal every time you want to run static analysis. You want a smooth, fast process integrated with your editor, keeping you informed every step of the way.

Fortunately, there are plenty of plugins and extensions for popular editors and IDEs that, once set up, will bring static analysis right alongside your coding.

A word of caution: Running static analysis can be slow, so you may not always want it running on every file save. Once you’ve ironed out most of the errors, it can be better to run the tools manually, or on something like a Git pre-commit hook, which runs automatically when you make commits.

With that in mind, let’s look at some common options for integration.

PHPStorm

Amazingly, PHPStorm has PHPCS and PHPMD support built in. It comes bundled with extensions for PHPStan and Psalm. And it has its own built-in static analysis and code quality tools.

We’ve already written about how we use PHPStorm on our WordPress projects and this includes a section on code quality. So that’s worth a read. But I’d also encourage you to check out the excellent PHPStorm documentation and go and explore the settings under Editor -> Inspections -> Quality Tools.

VS Code

In the virtual Delicious Brains office, the battle-of-the-IDEs tends to be between PHPStorm and VS Code, with some Vim keybindings thrown in for good measure.

VS Code has a wealth of extensions available for integrating with static analysis tools. So once you’re up and running with a decent VS Code setup for PHP and WordPress, I’d suggest adding:

  • PHP Sniffer for PHP CodeSniffer integration.
  • PHPStatic Analysis for PHPStan. Similar extensions exist for Psalm and Phan too.
  • PHP DocBlocker, which helps generate PHP docblocks from your existing code. A real time saver!

I’m sure that packages/extensions exist for Sublime Text, Vim, and other editors too. There is a great existing landscape of tools that make the benefits of static analysis more accessible.

Getting Started with Static Analysis in JavaScript

Now that you have a taste for static analysis, you may be wondering, “Can I do this in JavaScript too?” JavaScript is infamous for ignoring types and preferring to guess what you want to do rather than encouraging you to be more specific. So surely the benefits of it are even greater than for PHP?

As I mentioned earlier, you sure can! For example, TypeScript may be something you have heard of or even used, but it’s a BIG step to go from regular JavaScript to TypeScript and you need a build process to “compile” it into regular JS.

So you’ll be glad to hear that there is an easier way to get started with static analysis in JavaScript, and that’s to use JSDoc docblocks.

To make life even easier, VS Code and PHPStorm both have a good amount of JavaScript static analysis built into them. In fact, we’re not going to install any extensions or plugins here to get going. We’ll use the built-in features of both editors to start improving our code.

JSDoc lets you add annotations and type declarations in comments to give the static analysis more information about your code.

I’ll get you started here, but the TruckJS overview is where I first learned about this, and I recommend you read it too.

If you’re in VS Code, you can turn on this additional analysis for all your projects by changing a setting:

"javascript.implicitProjectConfig.checkJs": true

Better, though, is to do it on a project-by-project basis by creating a file in the root of your project called jsconfig.json containing, at a minimum:

{
    "compilerOptions": {
        "checkJs": true
    }
}

This is usually a better option because it:

  1. Allows you to have project-specific settings.
  2. Syncs settings between people on your team, ensuring a consistent approach.
  3. Provides more configuration options, such as excluding directories like your node_modules folder.

With this in place, and with a tag reference and a list of JavaScript’s built-in types, you should be ready to get started.

JavaScript static analysis inside PHPStorm.

JSDoc is powerful enough to do all the things that TypeScript does. But don’t be overwhelmed by it. Learn the basic type tags first and start adding them. See if your IDE spots any errors, and enjoy the extra information everyone has about how your code works.

Useful Resources

Hopefully, you are intrigued and keen to find out more. In addition to the links throughout this article, I’ve compiled a list of useful resources to help you learn more and take your next steps:

PHP Static Analysis Tools

WordPress-Specific Extensions

Learning Resources

Final Notes

Let’s be clear: static analysis is not for every developer and every project. I love it because my early software career taught me to think in types a lot. I’m happy to accept some visual “clutter” in my code if it gives me additional assurances that it’s robust and well documented, and so that I can make use of the automated checks that the analysis tools provide.

But not everyone likes having docblocks or type declarations everywhere. I’ll let you and your team decide. But I hope you’ll give it a try and start to see some of the benefits of strong typing and static analysis.

My goal here was to get you interested and equip you to get started and go and find out more. It may seem scary, but there aren’t actually many steps to getting started and it’s safe to run analyzers on your code to see what it makes of them.

I hope you will quickly find the benefits of static analysis and get hooked on it. It’s definitely a hot topic in the PHP community right now with great tools and tutorials around. So why not get ahead of the curve, see if it improves your code, and gets your team working better together?

What’s your experience of adding static analysis to WordPress projects? Let us know in the comments.

About the Author

Ross Wintle Software Developer

Ross is a software developer based in Swindon in the UK who specialises in WordPress and Laravel. He enjoys solving all kinds of complex, real world problems with code and loves helping other developers out. In a previous life he worked on aerospace systems; websites have always felt more down to earth!