The other day a co-worker asked me why the following snippet of code was acting weird:
1 2 3 4 5 6 7 8 | <?php $box = 'Tall box'; if(stripos($box, 'wide ') == 0) { echo "It's a wide box!"; } else { echo "It's a tall box!"; } ?> |
C:\> php test.php
It's a wide box!
I’m sure any seasoned PHP devs will spot the error straight away, but it took us a couple of minutes.
From the PHP docs for stripos:
int stripos ( string $haystack , string $needle [, int $offset = 0 ] )
Returns the numeric position of the first occurrence of needle in the haystack string.Unlike strpos(), stripos() is case-insensitive.
…
Returns the position as an integer. If needle is not found, stripos() will return boolean FALSE.
This is all well and good, except that in PHP, (0 == false) == true. The PHP interpreter tries to guess the types of both sides of the evaluation, and comes to the conclusion that false is equivalent to 0 (which is in turn equivalent to both NULL and empty string). This is correct in a lot of circumstances, but not great if you are specifically looking for the return value 0.
The solution? In PHP 4 the === operator was introduced (comparison operators). This gets rid of type-juggling (or inference):
===: TRUE if $a is equal to $b, and they are of the same type.
Contrast this simple task to how you might approach it in C#1.
1 2 3 4 5 6 7 8 9 | string box = "Tall box"; if (box.IndexOf("wide ") == 0) { Console.WriteLine("It's a wide box!"); } else { Console.WriteLine("It's a tall box!"); } |
The difference here is that String.IndexOf(...) will return -1 if the substring isn’t found.
Working with a statically typed language for a number of years, to me the whole idea of having to deal with a return value which may be this type or that type just feels a bit wrong. One thing worth noting though is that this is not necessarily true in all dynamically typed languages. For instance, Python is a lot more sensible in this scenario.
1 2 3 4 5 6 7 8 | >>> None == False False >>> 0 == False True >>> "test".index("hello") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not found |
This issue pops up in other PHP library functions (strtok(), current(), readdir() to name a few) maybe due to the standard library’s endemic inconsistencies. There are warnings on the official documentation and the functionality seems to be widely accepted – but I still can’t help feeling that this is counter-intuitive and must be yet another sticking point for new PHP programmers. Python is a great example of a middle ground between PHP and C# – strongly typed but also dynamic. Maybe that’s part of the reason why PHP’s position in the TIOBE is gradually falling while Python’s is rising.
1
String.StartsWith() would be better for this specific case.
Nice tips! I have been hunting for everything like that for quite a while these days. Many thanks!