The Quirks And Gotchas of PHP
The Quirks And Gotchas of PHP
If you come from a JavaScript background, you'll likely be familiar with some of its famous quirks, such as 1 + "1" equaling "11". Well, PHP has its own set of quirks and gotchas, too. Some are oddly similar to JavaScript's, while others can surprise a JavaScript developer.
Let's start with the more familiar ones.
1. Type Juggling and Loose Comparisons
Like JavaScript, PHP has two types of comparison operators: strict and loose. The loose comparison operator in PHP uses ==, while the strict comparison operator uses ===.
Here's an example of a loose vs. strict comparison in PHP:
`
PHP is a loosely typed language, meaning it will automatically convert variables from one type to another when necessary, just like JavaScript. This is not only when doing comparisons but also, for example, when doing numeric operations. Such conversions can lead to some unexpected results if you're not careful:
`
As you can see, the type system has gotten a bit stricter in PHP 8, so it won't let you commit some of the "atrocities" that were possible in earlier versions, throwing a TypeError instead. PHP 8 introduced many changes that aim to eliminate some of the unpredictable behavior; we will cover some of them throughout this article.
1.1. Truthiness of Strings
This is such a common gotcha in PHP that it deserves its own heading. By default, PHP considers an empty string as false and a non-empty string as true:
`
But wait, there's more! PHP also considers the string "0" as false:
`
You might think we're done here, but no! Try comparing a string such as "php" to 0:
`
Until PHP7, any non-numeric string was converted to 0 when cast to an integer to compare it to the other integer. That's why this example will be evaluated as true. This quirk has been fixed in PHP 8.
For a comprehensive comparison table of PHP's truthiness, check out the PHP documentation.
1.2. Switch Statements
Switch statements in PHP use loose comparisons, so don't be surprised if you see some unexpected behavior when using them:
`
The New Match Expression in PHP 8
PHP 8 introduced the match expression, which is similar to switch but uses strict comparisons (i.e., === under the hood) and returns a value:
`
Unlike switch, there is no "fall-through" behavior in match, and each branch must return a value, making match a great alternative when you need a more precise or concise form of branching—especially if you want to avoid the loose comparisons of a traditional switch.
1.3 String to Number Conversion
In earlier versions of PHP, string-to-number conversions were often done silently, even if the string wasn’t strictly numeric (like '123abc'). In PHP 7, this would typically result in 123 plus a Notice:
`
In PHP 8, you’ll still get int(123), but now with a Warning, and in other scenarios (like extremely malformed strings), you might see a TypeError. This stricter behavior can reveal hidden bugs in code that relied on implicit type juggling.
Stricter Type Checks & Warnings in PHP 8
- Performing arithmetic on non-numeric strings:
As noted, in older versions, something like "123abc" + 0 would silently drop the non-numeric part, often producing 123 plus a PHP Notice. In PHP 8, such operations throw a more visible Warning or TypeError, depending on the exact scenario.
- Null to Non-Nullable Internal Arguments:
Passing null to a function parameter that’s internally declared as non-nullable will trigger a TypeError in PHP 8. Previously, this might have been silently accepted or triggered only a warning.
- Internal Function Parameter Names:
PHP 8 introduced named arguments but also made internal parameter names part of the public API. If you use named arguments with built-in functions, be aware that renaming or reordering parameters in future releases might break your code. Always match official parameter names as documented in the PHP manual.
Union Types & Mixed
Since PHP 8.0, we can declare union types, which allows you to specify that a parameter or return value can be one of multiple types. For example:
`
Specifying the union of types your function accepts can help clarify your code’s intent and reveal incompatibilities if your existing code relies on looser type checking, preventing some of the conversion quirks we’ve discussed.
2. Operator Precedence and Associativity
Operator precedence can lead to confusing situations if you’re not careful with parentheses. For instance, the . operator (string concatenation similar to + in JavaScript) has left-to-right associativity, but certain logical operators have lower precedence than assignment or concatenation, leading to puzzling results in PHP 7 and earlier:
`
PHP 8 has fixed this issue by making the + and - operators take a higher precedence.
3. Variable Variables and Variable Functions
Now, we're getting into unfamiliar territory as JavaScript Developers. PHP allows you to define variable variables and variable functions. This can be a powerful feature, but it can also lead to some confusing code:
`
In this example, the variable $varName contains the string 'hello'. By using $$varName, we're creating a new variable with the name 'hello' and assigning it the value 'world'.
Similarly, you can create variable functions:
`
4. Passing Variables by Reference
You can pass variables by reference using the & operator in PHP. This means that any changes made to the variable inside the function will be reflected outside the function:
`
While this example is straightforward, not knowing the pass-by-reference feature can lead to some confusion, and bugs can arise when you inadvertently pass variables by reference.
5. Array Handling
PHP arrays are a bit different from JavaScript arrays. They can be used as both arrays and dictionaries, and they have some quirks that can catch you off guard. For example, if you try to access an element that doesn't exist in an array, PHP will return null instead of throwing an error:
`
Furthermore, PHP arrays can contain both numerical and string keys at the same time, but numeric string keys can sometimes convert to integers, depending on the context>
`
In this example:
- "1" (string) and 1 (integer) collide, resulting in the array effectively having only one key: 1.
- true is also cast to 1 as an integer, so it overwrites the same key.
And last, but not least, let's go back to the topic of passing variables by reference. You can assign an array element by reference, which can feel quite unintuitive:
`
6 Checking for Variable Truthiness (isset, empty, and nullsafe operator)
In PHP, you can use the empty() function to check if a variable is empty. But what does "empty" mean in PHP? The mental model of what's considered "empty" in PHP might differ from what you're used to in JavaScript. Let's clarify this:
The following values are considered empty by the empty() function:
- "" (an empty string)
- 0 (0 as an integer)
- 0.0 (0 as a float)
- "0" (0 as a string)
- null
- false
- [] (an empty array)
This means that the following values are not considered empty:
- "0" (a string containing "0")
- " " (a string containing a space)
- 0.0 (0 as a float)
- new stdClass() (an empty object)
Keep this in mind when using empty() in your code, otherwise, you might end up debugging some unexpected behavior.
Undefined Variables and isset()
Another little gotcha is that you might expect empty() to return true for undefined variables too - they contain nothing after all, right? Unfortunately, empty() will throw a notice in such case. To account for undefined variables, you may want to use the isset() function, which checks if a variable is set and not null:
`
The Nullsafe Operator
If you have a chain of properties or methods that you want to access, you may tend to check each step with isset() to avoid errors:
`
In fact, because isset() is a special language construct and it doesn't fully evaluate an undefined part of the chain, it can be used to evaluate the whole chain at once:
`
That's much nicer! However, it could be even more elegant with the nullsafe operator (?->) introduced in PHP 8:
`
If you’ve used optional chaining in JavaScript or other languages, this should look familiar. It returns null if any part of the chain is null, which is handy but can also hide potential logic mistakes — if your application logic expects objects to exist, silently returning null may lead to subtle bugs.
Conclusion
While PHP shares a few loose typing quirks with JavaScript, it also has its own distinctive behaviors around type juggling, operator precedence, passing by reference, and array handling. Becoming familiar with these nuances — and with the newer, more predictable features in PHP 8 — will help you avoid subtle bugs and write clearer, more robust code. PHP continues to evolve, so always consult the official documentation to stay current on best practices and language changes....