PHP's Oddities | flow2
Home
Archive
Projects
About
Search
I've been coding in PHP at work for the last 5 years. My org's entire backend is written in PHP—a decision made in 2007 when the company first started. It's not a language I ever imagined myself using prior to working there, but life takes you in all sorts of directions you don't expect.
PHP gets a bad rep in the industry, despite being a mature and commonly used language. But it's mostly based on out-of-date understanding of what PHP can do. Recent versions have caught up with most other languages in terms of features; by this point it's a pretty versatile general-purpose language. Certainly not just for serving HTML, as it was originally designed.
I'm no longer working at the aforementioned company, so I'm reflecting on my experience with PHP after all these years and there's some things I've always found odd about it.
And more than just odd, some of it's language features are really unintuitive and have been prone to cause bugs. This comes from personal experience and many previous headaches at work. I'll explain two of the biggest offenders in this post—in short:
Arrays are weird and overloaded
The type system is clunky
Arrays Are Not Really Arrays
PHP's standard library basically only has one data structure: the array. This was intentional; it was designed to be a general-purpose, flexible data structure that can cover a variety of use cases. It's technically an ordered key-value dictionary, not an array in the traditional sense.
Unfortunately, with flexibility comes complexity. If you want to create a collection of fixed-size objects in an allocated memory block, you can't really do that. PHP pretends to support them, but the illusion breaks down in unexpected ways.
Let's say I have a bunch of fruits. PHP let's me define a fruits "array" and I can do normal array things with it.
$fruits = ["apples", "oranges", "limes"];
// you can count em'<br>count($fruits) // 3
// you can access em'<br>$fruits[0] // "apples"
// you can print em'<br>print_r($fruits);<br>/*<br>Array<br>[0] => "apples"<br>[1] => "oranges"<br>[2] => "limes"<br>*/
Everything looks fine but you get into trouble whenever you perform a mutation on this "simple" array; it will be exposed as being a key-value store.
When you use one of PHP's built-in functions for standard array operations like sorting or filtering, it will operate on the keys AND values of your array. If it mutates the array in-place or by a return value, the key order will likely become inconsistent.
// this will mess up your array<br>$filteredFruits = array_filter($fruits, fn ($name) => str_contains($name, "limes"));
print_r($filteredFruits);<br>/*<br>Array<br>[2] => limes<br>*/
// getting the first element won't work anymore<br>print($filteredFruits[0]);<br>/*<br>PHP Warning: Undefined array key 0<br>*/
// element removal also messes up the array<br>unset($fruits[0]);<br>print_r($fruits);<br>/*<br>Array<br>[1] => oranges<br>[2] => limes<br>*/
why can't I hold all these indices???
The only way to put these arrays back into a naturally indexed state is to use the array_values() function. You just have to know that, or else you end up with subtle bugs.
$filteredFruitsFixed = array_values($filteredFruits);<br>print_r($filteredFruitsFixed);<br>/*<br>Array<br>[0] => limes<br>*/
$fruitsFixed = array_values($fruits);<br>print_r($fruitsFixed);<br>/*<br>Array<br>[0] => oranges<br>[1] => limes<br>*/
It's just strange to me that PHP doesn't support simple collections of objects. It's annoying to have to manage these arbitrary numeric keys when all you really want is ordinal indexing like 99% of the time. It feels like a leaky abstraction.
Class Property Types Are Confusing
In PHP5, a native type system was added to the language. It was expanded over time and by PHP7 you could define the types for your class's properties. Although PHP is a scripting language, type declarations will help catch bugs during testing, or even during development with the use of static analysis tools like PHPStan.
But PHP's type system has some quirks since it was built on an existing dynamically typed language. The rules had to be designed after the behaviour was already there. For class properties, there's a hidden uninitialized state that can pop up if you're not careful.
Let's define a Book class with three string properties:
class Book {<br>public $title;<br>public string $author;<br>public ?string $publisher;
Here, I'm illustrating all the ways of declaring the type for a string property:
Don't
It's a string
It's nullable string
Before PHP7, all class properties were (1): untyped. Since the type system is optional, it has to live alongside the "legacy" behaviour which has weird consequences. For example, what do you think the values of these three properties will be after we instantiate a Book object?
$b = new Book();<br>print_r($b);<br>/*<br>Book Object(<br>[title] =><br>*/
Trick question! Only the untyped $title property will have a value, and that value is NULL. That seems fine and is roughly in line with how I'd expect a...