Detecting hidden bugs in PHP code using PHP_CodeSniffer
Tags: codequality, staticanalysis, php, programming
Published: 2017-12-27 14:55:57

---
title: "Detecting hidden bugs in PHP code using PHP_CodeSniffer"
published: true
tags: code-quality,static-analysis,php,programming
canonical_url: https://www.moxio.com/blog/10/detecting-hidden-bugs-in-php-code-using-php-codesniffer
---

![](https://cdn-images-1.medium.com/max/1024/1*bZImz4zCSge6Cb1uO5gQGA.jpeg)

Although PHP serves us well as a programming language, we cannot deny that some of its behavior can be very surprising. If one is not aware of these pitfalls, this can easily lead to hidden bugs in PHP code. In fact, we have ran into a fair share of these issues ourselves, but try to take measures to prevent being struck by them again. In this post we will look into two potential pitfalls in PHP, and how these can be detected and avoided using our open-sourced collection of sniffs for PHP\_CodeSniffer.

### Fool me once, shame on&nbsp;you

Quick, what does the following code&nbsp;do?

```
<?php 
$chars = str_split("The quick brown fox jumps over the lazy dog."); foreach ($chars as $char) {
    switch (strtoupper($char)) {
        case "A": 
        case "E": 
        case "I": 
        case "O": 
        case "U": 
        case "Y": 
            continue; 
    }

print $char; 
}
```

It’s not strange to think that this code fragment will print the input string with all vowels removed. However, it will in fact just print the original string, including the vowels ([see it for yourself](https://3v4l.org/qBcK4)). This behavior is caused by the fact that PHP [considers a](http://php.net/manual/en/control-structures.continue.php)[switch-statement a looping structure](http://php.net/manual/en/control-structures.continue.php) for the purpose of continue. The continue thus jumps to the end of the switch-statement (rather than to the end of the foreach-body) and all vowels are still printed. If we want to skip printing the vowels in the example above, we would have to use continue 2 to jump to the end of the foreach-body.

I recently bumped into this bug feature interesting behavior in a piece of code. Even though I had seen an example similar to the one above before, and thus should have known this intricacy, I still spent way too much time trying to find out why my code did not produce the desired&nbsp;results.

### Fool me twice, shame on&nbsp;me

To prevent getting bitten by this quirk again, we decided to implement something for automated detection of the above situation and warn us about its unexpected behavior. For this purpose we chose to write a custom _sniff_ for [PHP\_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), which we already used for enforcing coding standards. The token- and scope-based approach used by PHP\_CodeSniffer makes it easy to check all switch-cases for top-level continue-statements (i.e. not within a nested looping structure) and see if they have a numeric 'level'-argument. We disallow any such continue-statements without an explicit number of looping levels to jump&nbsp;over:

```
<?php
for ($i = 1; $i < 10; $i++) {
    switch ($x) {
        case "foo": 
            continue; // NOT OK, probably a bug 
        case "bar": 
            foreach ($a as $k => $v) {
                continue; // OK, inside a nested 'foreach' 
            }
            continue 2; // OK, explicit 'level'-argument 
    } 
}
```

This warns us about potential hidden bugs like the one above. If we get an error from PHP\_CodeSniffer but the behavior of continue is actually what we want (although I cannot imagine why one would not use break in such a case), we can always replace the continue by continue 1 to confirm that we have thought this case through and suppress the&nbsp;error.

### Fool me three times,&nbsp;…

If only this was the sole hidden pitfall when working with PHP… Due to all problems that come with non-strict comparisons we already have a check in place to enforce strict comparison operators (=== etc.) over their non-strict counterparts (== etc.). However, there are still some PHP functions that will surprise you with non-strict comparison behavior by default, like [in\_array](https://twitter.com/fabpot/status/460707769990266880) and array\_search.

That’s why we have also implemented a PHP\_CodeSniffer sniff that requires the $strict-parameter to such functions to be set explicitly:

```
<?php 
in_array("foo", [0]); // NOT OK, might introduce hidden bugs in_array("foo", [0], true); // OK, strict comparison
in_array("foo", [0], false); // OK, you have probably thought about this
```

Although we can still opt in to the non-strict behavior, this should prevent us from having to deal with the intricacies of non-strict comparison if we have not explicitly asked for&nbsp;them.

### … release as open-source

To help the rest of the PHP community avoid these pitfalls, we have decided to open-source [our PHP\_CodeSniffer sniffs](https://github.com/Moxio/php-codesniffer-sniffs). They can easily be added as a development dependency into other projects using the [Composer package](https://packagist.org/packages/moxio/php-codesniffer-sniffs), either as a standalone ruleset or by integrating them into your own PHP\_CodeSniffer standard.

We have a backlog of more PHP pitfalls that we want to implement checks for, so stay tuned (by following us on [Twitter](https://twitter.com/moxio) or subscribing to our [RSS feed](https://www.moxio.com/rss/blog)) for more&nbsp;sniffs!

_Originally published at_ [_www.moxio.com_](https://www.moxio.com/blog/10/detecting-hidden-bugs-in-php-code-using-php-codesniffer)_._

* * *