LmCast :: Stay tuned in

PHP's Oddities

Recorded: May 23, 2026, 3:58 p.m.

Original Summarized

PHP's Oddities | flow2

Home
Archive
Projects
About

Home
Archive
Projects
About
Search

In Tech

PHP's Oddities

May 19, 2026

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'
count($fruits) // 3

// you can access em'
$fruits[0] // "apples"

// you can print em'
print_r($fruits);
/*
Array
(
[0] => "apples"
[1] => "oranges"
[2] => "limes"
)
*/

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
$filteredFruits = array_filter($fruits, fn ($name) => str_contains($name, "limes"));

print_r($filteredFruits);
/*
Array
(
[2] => limes
)
*/

// getting the first element won't work anymore
print($filteredFruits[0]);
/*
PHP Warning: Undefined array key 0
*/

// element removal also messes up the array
unset($fruits[0]);
print_r($fruits);
/*
Array
(
[1] => oranges
[2] => limes
)
*/

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);
print_r($filteredFruitsFixed);
/*
Array
(
[0] => limes
)
*/

$fruitsFixed = array_values($fruits);
print_r($fruitsFixed);
/*
Array
(
[0] => oranges
[1] => limes
)
*/

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 {
public $title;
public string $author;
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();
print_r($b);
/*
Book Object(
[title] =>
)
*/

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 language to use a NULL value. But the other two properties will NOT have a value because they don't exist, or rather they could exist but haven't been initialized yet.
This example exposes the "uninitialized" state that a property can be in, which is NOT the same as NULL. This distinction frustratingly comes up when you try to do a null check on these properties:
print("title is null: " . is_null($b->title));
/*
title is null: 1
*/

print("author is null: " . is_null($b->author));
/*
PHP Fatal error: Uncaught Error: Typed property Book::author must not be accessed before initialization...
*/

print("publisher is null: " . is_null($b->publisher));
/*
PHP Fatal error: Uncaught Error: Typed property Book::publisher must not be accessed before initialization...
*/

Not a warning—a FATAL error occurs if you try to access an uninitialized property. This comes up a lot in cases where you try to deserialize data into a PHP object. If a field's data isn't present you might not initialize the property at all.

ahh yes, NULL...who was that by again?
This lax behaviour for property definitions makes writing code around them harder. Especially when you take into account that any object can have properties dynamically added to them:
$b->foo = "bar";
print("foo: " . $b->foo);
/*
foo: bar
*/

So I feel like the class property type system does little to help you understand what a given object is composed of, and in some respects has made it less clear because it's introduced this new uninitialized state. As a developer, it's hard to write defensive code because you're never sure which checks to do for all these situations: is_null(), isset(), property_exists(), empty()... it's not obvious which functions cover which states.
I'd argue that uninitialized did not need to be a state at all. For nullable typed properties, just default them to null the way untyped properties are. And for non-nullable types, require them to be be defined as constructor promoted parameters OR require a default value at declaration. Similar requirements already exist for the readonly attribute, so it's certainly feasible for the PHP execution engine to enforce it.
But there's probably some nuance or historical reason I'm missing here. Let me know in the comments if you know.
Conclusions
Despite all the critiquing I've done in this article, I still think the amount of hate PHP gets is undeserved. Like any language, it has it's quirks and tradeoffs, but you can still accomplish any task using PHP that you could in another language. The more you know about a language, the better you can structure things to work "with the grain" and write more idiomatic code.
Some things I do enjoy about PHP:

It's a scripting language, so development friction is low. Make a file change and it instantly takes effect.
Laravel is a solid web framework with tons of extensible functionality. It's opinionated and definitely leans into the "auto-magical" framework style, but it was designed well so you don't mind.
All the $ signs help remind you what you're doing it all for at the end of the day 🤑

Thanks for reading!

#coding #languages #PHP

Previous Post

Next Post

Copyright © 2026 Joe Boudreau
The source code of this website is licensed under GNU GPLv3
The content of this website is licensed under Creative Commons Attribution 4.0 International License.

The author reflects on their five years of working with PHP, noting that despite PHP being a mature and widely used language, it carries a reputation that stems partly from outdated understandings and certain unintuitive language features that have historically caused bugs. The author highlights two primary areas where PHP exhibits these oddities: its handling of arrays and its type system for class properties.

Regarding arrays, the author posits that PHP’s standard library is heavily centered around the array data structure, which is technically an ordered key-value dictionary rather than a traditional array. This design choice introduces complexity, particularly when performing mutations or operations on these structures. When built-in functions such as sorting or filtering are applied to an array, they operate on both the keys and values, which can lead to inconsistent key ordering when the array is mutated in-place. This behavior often results in unexpected outcomes, such as the loss of expected array indices or the inability to access elements after operations like array_filter or unset. To mitigate these issues and ensure predictable behavior, the author points out the necessity of using functions like array_values() to reset the indices after mutable operations, emphasizing that developers must be acutely aware of the array's internal structure to avoid subtle bugs.

The second area of complexity concerns PHP's type system, especially in relation to class properties. Although type declarations were added incrementally, the system inherits quirks from its dynamic nature, leading to an "uninitialized state" that complicates defensive programming. The author illustrates this with an example involving a class where properties are declared with specific types, such as strings or nullable strings. Before PHP7, or in the general dynamic context, class properties can exist in an uninitialized state that is distinct from being null. When an object is instantiated, properties that are not explicitly assigned might not exist at all, leading to fatal errors when attempting to access them. This distinction between an uninitialized state and a null value creates confusion for developers attempting to implement robust null checks; checking for null does not cover the possibility of an uninitialized property, which is exacerbated when properties can be dynamically added to objects. The author suggests that this lax behavior makes writing defensive code difficult, as developers must constantly consider properties that are unset versus those that simply hold a null value, often requiring the use of functions like isset(), is_null(), and property_exists() to navigate these subtle states.

In conclusion, despite these noted architectural oddities, the author maintains that the negative perception of PHP is somewhat undeserved. The language possesses understandable tradeoffs, but it remains capable of accomplishing any task. The author appreciates aspects of PHP, such as the low development friction inherent in its scripting nature and the robust functionality provided by frameworks like Laravel. Ultimately, the author concludes that gaining a deeper understanding of a language allows a developer to structure code to work with its inherent characteristics, leading to more idiomatic and effective programming.