0

Type juggling and implicit coercion (the dark side)

Beginner5 min read·php-02-004
interviewcompare

Concept

PHP's type juggling — the automatic conversion of values between types — is one of the most frequently tested interview topics because it contains genuinely surprising behavior that causes real bugs. Understanding it is not about memorizing edge cases; it's about understanding the underlying rules so you can predict behavior you've never seen before.

The type juggling rules

PHP uses type juggling in: loose comparisons (==), arithmetic operations, string interpolation, function calls without strict types, and built-in functions.

String to number conversion: PHP extracts a numeric prefix from the string. "42abc" becomes 42. "abc" becomes 0. "3.14text" becomes 3.14. This is the source of many bugs.

Bool conversions: The following values are false in boolean context; everything else is true:

  • false itself
  • 0 (integer zero)
  • 0.0 (float zero)
  • "" (empty string)
  • "0" (the string "zero" — this is the famous one)
  • [] (empty array)
  • null
  • Unset/undefined variables

Array arithmetic: When PHP needs a number and gets an array, it throws a TypeError (PHP 8.0+). Before PHP 8, arrays in numeric contexts produced E_NOTICE and evaluated to 0 or 1.

The "0" string gotcha

"0" is a non-empty string but is false in boolean context. This means:

  • if ("0") is false
  • empty("0") is true
  • (bool) "0" is false

This surprises most developers because "false" (the literal string "false") is true — it's a non-empty, non-"0" string.

Comparison juggling

The == operator applies type juggling before comparing. The most famous case: before PHP 8, 0 == "abc" was true because "abc" juggled to 0. PHP 8 fixed this: comparing an int to a non-numeric string now returns false instead of coercing. But many other juggling behaviors remain.

ExpressionPHP 7PHP 8
0 == "abc"truefalse
0 == ""truefalse
0 == "0"truetrue
"1" == "01"truetrue
"10" == "1e1"truetrue
100 == "1e2"truetrue
null == falsetruetrue
null == 0truetrue
null == ""truetrue
null == "0"falsefalse

Code Example

php
<?php
declare(strict_types=1);

// Type juggling demonstrations — understanding, not just memorizing

// String to integer conversion
var_dump((int) "42abc");    // int(42)
var_dump((int) "abc");      // int(0) — no numeric prefix
var_dump((int) "");         // int(0)
var_dump((int) "0x1A");     // int(0) — hex strings NOT converted by (int)cast
var_dump(intval("0x1A", 16)); // int(26) — use intval with base for hex

// The "0" string gotcha
$userInput = "0"; // e.g., user answered question zero
if ($userInput) {
    echo "User answered\n";
} else {
    echo "No answer (WRONG — user answered '0'!)\n"; // This runs!
}
// Fix: check explicitly
if ($userInput !== "") {
    echo "User answered correctly\n"; // This runs
}

// Arithmetic type juggling
$result = "10" + 5;      // int(15) — "10" juggled to 10
$result2 = "10.5" + 5;   // float(15.5)
$result3 = "10abc" + 5;  // int(15) with E_WARNING about non-numeric value

// Boolean juggling table
$falsy = [false, 0, 0.0, "", "0", [], null];
foreach ($falsy as $val) {
    $type = gettype($val);
    $repr = var_export($val, true);
    echo "(bool) {$repr} ({$type}) = " . var_export((bool) $val, true) . "\n";
}

// PHP 8 vs PHP 7 comparison change
// PHP 8: comparing int 0 to non-numeric string → false
$token = "abc123";
if (0 == $token) {
    echo "Match (PHP 7 behavior — dangerous!)\n";
} else {
    echo "No match (PHP 8 — correct)\n"; // PHP 8+ runs this
}

// Real-world bug: in_array() with loose comparison (PHP 7 style)
$allowedIds = [1, 2, 3];
var_dump(in_array("abc", $allowedIds));    // PHP 7: true! PHP 8: false
var_dump(in_array("abc", $allowedIds, true)); // Always false — strict mode

// Always use strict in_array:
function isAllowedId(int|string $id, array $allowed): bool
{
    return in_array($id, $allowed, strict: true);
}

Interview Q&A

Q: What is the most dangerous type juggling behavior in PHP and how did PHP 8 fix it?

The most dangerous was 0 == "any-string" evaluating to true in PHP 7 because non-numeric strings coerced to 0, making 0 == $userToken true for any non-numeric token. This was a real security vulnerability: code like if ($storedHash == $userHash) could allow authentication bypass if $userHash was a string like "0e123456" (a magic hash) compared against a stored hash that was also a string starting with 0e — both evaluated to 0.0 as floats. PHP 8 changed the comparison rules: when comparing an integer to a non-numeric string, PHP now converts the string to a number for the comparison only if the string IS numeric, otherwise it returns false instead of coercing. This eliminated the magic hash vulnerability and made comparisons more predictable.


Q: Why is empty("0") true in PHP when "0" is a non-empty string?

empty() is not a pure emptiness check — it is a combined "undefined or falsy" check. The "0" string is falsy because of a special rule in PHP's truthiness system: the only non-empty string that evaluates to false is "0" (the string representation of zero). PHP's designers made this choice for historical consistency with numeric string comparisons. The practical consequence: never use empty() to validate user input where "0" is a valid value (e.g., a form field where the user types 0). Use isset($var) && $var !== "" or strlen($var) > 0 instead. Alternatively, use !== '' to check for non-empty string regardless of value.


Q: How does PHP handle arithmetic operations on non-numeric strings in PHP 8 vs PHP 7?

In PHP 7, using a non-numeric string in arithmetic ("abc" + 5) would trigger an E_NOTICE and silently return 5 (the string coerced to 0). In PHP 8.0, this was upgraded to E_WARNING. In PHP 8.1, "abc" + 5 still produces a result (5) but throws E_WARNING: A non-numeric value encountered. The operation does not throw a TypeError for arithmetic — only function calls with typed parameters do. For a string with a numeric prefix like "10abc", PHP 8.1 produces E_DEPRECATED and will remove the coercion in a future version. With strict_types=1, none of this changes for arithmetic — strict types only governs function parameter coercion, not arithmetic operations.