strpos() Syndrom: When 0 And false Are Not The Same
In PHP, several functions can return 0 or false or even null, and all these values may be confused for different things.
The most popular function with this syndrom is strpos(), hence the name of this classic PHP bug. strpos() returns the position where the string was found. In the case of b below, it returns 1. And 1 is truthy.
When strpos() does not find the requested string, as it is the case for d, then it returns false.
<?php
if (strpos('abc', 'd')) {
print "d is found!\n";
} else {
print "d is not found!\n";
}
if (strpos('abc', 'b')) {
print "b is found!\n";
} else {
print "b is not found!\n";
}
if (strpos('abc', 'a')) {
print "a is found!\n";
} else {
print "a is not found!\n";
}
>
The problem arise when strpos() finds the requested string on the first position of the haystack. Then, the returned position is 0, which is falsy. At this point, string not found and string found at position 0 are confused, and it leads to a bug.
In this case, an error is converted to a false value, while a valid return value may also be confused with false, such as 0, '' (empty string), false, [] (empty array) or null. Nowadays, PHP tends to throw exceptions to avoid confusing one for the other, such as with json_decode(), though some previous behaviors are still in place.
There are some notable PHP functions which produce this behavior:
String Functions
strpos()– Returns the position (0 or higher) orfalseif not found.stripos()– Case-insensitive version ofstrpos().strrpos()– Returns the position of the last occurrence orfalse.strripos()– Case-insensitive version ofstrrpos().strstr()– Returns the substring orfalseif not found.stristr()– Case-insensitive version ofstrstr().strpbrk()– Returns the substring orfalseif not found.
Array Functions
array_search()– Returns the key (which can be0) orfalseif not found.
Other Functions
preg_match()– Returns1for a match,0for no match, orfalseon error.preg_match_all()– Returns the number of matches (which can be0) orfalseon error.json_decode()– May return valid decodedfalse, 0 ornullvalues.
Best Practices
Strict comparison
One solution to protect one’s code against this problem is to always use a strict comparison, with the === or !== operators. )** when checking the return value of these functions to avoid confusion between 0 and false.
Example: “`
<?php
<div><pre><code class="language-none">if (strpos($haystack, $needle) !== false) {
// Found
}</code></pre></div>
>
“`
Use safe functions
Another solution is to use dedicated functions, such as str_contains(), str_start_with()or str_end_with(), instead of strpos().
There isn’t always a straightforward replacement for the orginal function, such as for json_decode(), or preg_match(). In that case, you should create it yourself, with the added context.
Example: “`
<?php
<div><pre><code class="language-none">// replace strpos() with str_contains()
if (str_contains($haystack, $needle)) {
// Found
}
function my_json_decode(string $json): string {
if ($json === 'null') {
throw new Exception("JSON cannot only hold NULL");
}
$return = json_decode($json) ?: '';
return $return;
}</code></pre></div>
>
“`
Use static analysis
Static analysis tools, like Exakat, are able to detect this error and focus attention on them before the code goes to production.
Explicit comparison
The strpos() syndrom is not limited to that function: it comes also with array_search(), and others. It usually takes a few hours of head scratching to find the error and make the code work again.
This is a PHP classic bug that should be known to every developer that work with the platform.

