3 / 12
Guide Book Β· Chapter 3

Types & type juggling

The ten types, how PHP converts between them, why `0 == "abc"` changed in PHP 8, and what `declare(strict_types=1)` really does.

PHP is dynamically typed: a variable has no type, the value in it does. Assigning a new value can change the type at any moment.

The types

TypeExampleNotes
nullnullthe only value of its type β€” "no value"
booltrue, false
int42, -7, 0x2Aplatform-sized, 64-bit almost everywhere
float3.14, 1e3IEEE-754 double
string'hi', "hi"a byte string, not a character string
array[1, 2]an ordered map
objectnew stdClass
callable'strlen', fn() => 1a type hint, not a storable type
resourcea file handlea handle to something outside PHP
never, voidβ€”return types only
php
<?php

foreach ([null, true, 42, 3.14, 'hi', [1, 2], new stdClass] as $v) {
    printf("%-10s %s\n", get_debug_type($v), var_export($v, true));
}

πŸ’‘ Tip: get_debug_type() is almost always what you want over gettype(). It returns the names you actually write β€” int, bool, float, and the class name for objects β€” where gettype() returns the historical integer, boolean, double.

Type juggling

PHP converts values automatically when the context demands a particular type. The value itself never changes β€” the conversion is temporary.

php
<?php

echo 5 + '3', "\n";          // numeric context:  8
echo 5 . '3', "\n";          // string context:   53
var_dump((bool) '0');        // logical context:  false
var_dump((bool) '0.0');      // ...but this one is true

What counts as false

Only these are falsy. Everything else β€” including '0.0', 'false', -1 and [0] β€” is truthy.

php
<?php

$falsy = [false, 0, -0.0, '', '0', [], null];

foreach ($falsy as $v) {
    printf("%-8s β†’ %s\n", var_export($v, true), var_export((bool) $v, true));
}

Comparison: == versus ===

=== compares type and value. == converts first. Prefer ===.

php
<?php

var_dump(1 === '1');     // false β€” different types
var_dump(1 == '1');      // true  β€” '1' is a numeric string
var_dump('1' == '01');   // true  β€” both numeric, compared as numbers
var_dump('10' == '1e1'); // true  β€” 1e1 is numeric notation for 10
var_dump(100 == '1e2');  // true

The PHP 8 change everyone should know

Before PHP 8, comparing a number with a string converted the string to a number β€” so 0 == "abc" was true, because "abc" became 0. Since PHP 8 the number is converted to a string when the string is not numeric, so the comparison is a string comparison.

php
<?php

var_dump(0 == 'abc');     // false in PHP 8+ (was true before)
var_dump(0 == '');        // false in PHP 8+ (was true before)
var_dump('abc' == '0');   // false
var_dump(0 == '0');       // true β€” '0' IS numeric

🧭 Scenario: Old code guarded a login with if ($password == 0). On PHP 7 any non-numeric password matched β€” a real vulnerability that shipped more than once. PHP 8 closes it, and === would have closed it a decade earlier.

Casting explicitly

php
<?php

var_dump((int) '42 apples');    // 42  β€” leading digits are taken
var_dump((int) 'apples 42');    // 0   β€” no leading digits
var_dump((float) '1.5e3');      // 1500.0
var_dump((string) true);        // "1"
var_dump((string) false);       // ""  β€” not "0"
var_dump((array) 'solo');       // ["solo"]

A non-numeric string in arithmetic is different from a cast: it throws.

php
<?php

try {
    echo 'apples' + 1;      // throws: Unsupported operand types
} catch (\TypeError $e) {
    echo 'TypeError: ', $e->getMessage(), "\n";
}

echo '5 apples' + 1, "\n";  // 6, plus a Warning: the ' apples' was discarded

Declared types and strict_types

Type declarations on parameters, returns and properties are checked at runtime. In the default coercive mode PHP will convert a value if it safely can.

php
<?php

function half(int $n): float {
    return $n / 2;
}

echo half(7), "\n";       // 3.5
echo half('8'), "\n";     // 4   β€” the numeric string is coerced to int

declare(strict_types=1) β€” which must be the very first statement in a file β€” turns coercion off for calls made from that file. Only int β†’ float widening survives.

php
<?php

declare(strict_types=1);

function half(int $n): float {
    return $n / 2;
}

echo half(7), "\n";

try {
    echo half('8');      // throws: must be of type int, string given
} catch (\TypeError $e) {
    echo 'TypeError: ', $e->getMessage(), "\n";
}

Union, nullable and intersection types

php
<?php

function parseId(int|string $id): ?int {
    return is_int($id) ? $id : (ctype_digit($id) ? (int) $id : null);
}

var_dump(parseId(7), parseId('12'), parseId('x7'));

?int is shorthand for int|null. A&B requires a value satisfying both interfaces. mixed accepts anything and is the explicit way to say "no constraint".

Reference: Types and Type juggling in the PHP Manual.

index.php
Loading editor…
No output yet
Press Run or βŒ˜β†΅ to compile and run your code.

Search

Jump to a lesson or a past run