skip to content

Juggling & Casting

How PHP converts between int, float, string and bool on its own and when you cast: numeric strings, arithmetic TypeError, falsy "0". Interviewers probe it because silent conversions hide bugs.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In PHP, which values convert to false in a boolean context, and why does the string '0' cause bugs?

level: juniorimportance: must knowfreq 58%

answer

  1. a short fixed list
  2. 0, 0.0, '', '0', [] and null
  3. '0' is falsy, '0.0' and ' ' are not
  4. objects are true
  5. compare explicitly instead

basics

~20 s

false, 0, 0.0, -0.0, '', '0', an empty array and null are falsy; everything else is true. '0' is the trap: a real value such as a zero quantity or the name '0' fails an if check.

solid answer

~40 s

Converting to `bool` is false for a short, fixed list: `false`, the integer `0`, the floats `0.0` and `-0.0`, the empty string `''`, the string **`'0'`**, an **empty array** `[]`, and `null`, which includes unset variables. A few internal objects that overload the cast, such as a GMP number holding 0, are also false. Everything else is true: `'0.0'`, `' '`, `'false'`, `[0]`, `-1` and every ordinary object. The `'0'` rule bites because it is a real value: a CSV quantity of `'0'`, a form field containing `0`, or a username `'0'` all fail `if ($value)`, so code treats valid zero as missing. The fix is to say what you mean: `$qty !== ''`, `$qty !== null`, `$items !== []` or `is_numeric($qty)`, rather than relying on truthiness.

code

php · 15 lines
php
<?php
declare(strict_types=1);

$rows = [['sku' => 'BX-1', 'qty' => '12'], ['sku' => 'BX-2', 'qty' => '0'], ['sku' => 'BX-3', 'qty' => '']];

foreach ($rows as $row) {
    if ($row['qty']) {                 // skips BX-2 and BX-3
        echo "truthy: {$row['sku']}\n";
    }
    if ($row['qty'] !== '') {          // keeps BX-2, skips only BX-3
        echo "present: {$row['sku']}\n";
    }
}

var_dump(array_filter(['0', '1', '']));  // [1 => '1']: '0' was dropped too

go deeper

for a junior

Recall the falsy list: false, 0, 0.0, '', '0', [] and null, and that everything else, including '0.0' and objects, is true.

for a middle

Explain why '0' is falsy while '0.0' is not, and how truthiness leaks into ?:, array_filter() and presence checks.

for a senior

Find truthiness checks on user and file input in review and replace them with explicit comparisons that keep zero and '0' as valid data.

for a principal

Encourage a codebase style where conditions compare explicitly on untyped input, reserving truthiness for values that are already booleans.

## What "boolean context" means PHP converts a value to `bool` whenever it needs a truth value: in `if`, `while` and `for` conditions, with `!`, `&&` and `||`, in the ternary `?:`, and with an explicit `(bool)` cast or `boolval()`. The conversion is the same everywhere, and it follows a **fixed list of falsy values**. ## The falsy list According to the manual, these convert to `false`: - `false` itself; - the integer `0`; - the floats `0.0` and `-0.0`; - the empty string `''` **and the string `'0'`**; - an array with zero elements, `[]`; - `null`, including variables that were never set; - internal objects that overload their bool conversion, for example a GMP object representing `0`. **Everything else is `true`.** That includes some values people expect to be false: | Value | `(bool)` | Why it surprises | |---|---|---| | `'0.0'` | `true` | only the exact string `'0'` is special | | `' '` | `true` | a space is a non-empty string | | `'false'` | `true` | a non-empty string | | `[0]` or `[null]` | `true` | the array has one element | | `-1` | `true` | any non-zero number | | `new stdClass()` | `true` | ordinary objects are always true | ## Why '0' is the classic bug The string `'0'` is falsy because PHP treats numeric strings like numbers in many places, and `0` is falsy. But `'0'` is also a perfectly ordinary piece of data: - a warehouse CSV row where the quantity column is `'0'`; - a form field where a user typed `0`; - a product code, house number or username that happens to be `'0'`. Code that checks presence with truthiness treats all of these as missing: ```php if ($row['qty']) { // false for '0': the row is skipped as "empty" $stock[$sku] = (int) $row['qty']; } ``` The zero-stock row never updates the stock table, so a sold-out item still shows its old quantity. The same trap appears with the short ternary: `$qty = $row['qty'] ?: 'unknown';` replaces a real `'0'` with `'unknown'`, because `?:` tests truthiness. Array helpers inherit the rule too: `array_filter()` without a callback removes every falsy element, so it silently drops `'0'` entries along with the empty ones. ## Checking what you actually mean Replace truthiness with the test that expresses your intent: | Intent | Write | |---|---| | the field is not the empty string | `$qty !== ''` | | the value was provided at all | `$qty !== null` or `array_key_exists('qty', $row)` | | the array has elements | `$items !== []` or `count($items) > 0` | | the value is a number | `is_numeric($qty)` or a validation filter | For `array_filter()`, pass an explicit callback, for example `static fn ($v) => $v !== ''`. ## When the string means a boolean The opposite problem appears when input *spells* a boolean: a CSV column `active` holding `'false'`, `'no'` or `'off'`. A cast gets these wrong, since `(bool) 'false'` and `(bool) 'no'` are both `true`. PHP's validation filter understands the words: - `filter_var($raw, FILTER_VALIDATE_BOOL)` returns `true` for `'1'`, `'true'`, `'on'` and `'yes'`, and `false` for `'0'`, `'false'`, `'off'`, `'no'` and `''`, case-insensitively. - With the `FILTER_NULL_ON_FAILURE` flag it returns `null` for anything else, so `'maybe'` is distinguishable from a real `false`. `FILTER_VALIDATE_BOOL` is the PHP 8 name; `FILTER_VALIDATE_BOOLEAN` is the older alias for the same filter. ## Rules of thumb 1. Use truthiness only when the value is **already a `bool`**, or when every falsy value genuinely means "absent" for your data. 2. For strings from users or files, compare against `''` explicitly; `'0'` is data. 3. For numbers, compare with the number: `$qty > 0` says more than `if ($qty)`. 4. Remember that `null` from a missing array key and `'0'` from a real field both read as `false`; if the difference matters, check for the key or for `null` first.

  • Is the string '0.0' falsy in PHP?
    No. Only the empty string and the exact string `'0'` are falsy. `'0.0'`, `'00'`, `' 0'` and `'false'` are non-empty strings that are not `'0'`, so they convert to `true`, even though some of them are numerically zero.
  • Why does $label = $qty ?: 'unknown'; misbehave when $qty is '0'?
    The short ternary `?:` returns its left operand only when that operand is truthy. `'0'` is falsy, so a real quantity of zero is replaced by `'unknown'`. When only `null` should trigger the fallback, use `??`, which tests for `null` rather than truthiness, or compare explicitly.
  • Is an object with no properties falsy?
    No. Ordinary objects are always `true`, whatever their contents; an empty `stdClass` included. Only some internal classes that overload the bool conversion, such as a GMP number holding 0, can be false.

saying these in an interview costs you the question

  • Says the string '0.0' is falsy because it equals zero.
  • Believes an empty object is falsy like an empty array.
  • Thinks the string 'false' converts to false.
  • Uses if ($qty) to check that a quantity field was filled in.
  • Says [0] is falsy because its only element is falsy.
open as a page

In PHP, what does an (int) cast return for strings such as '42', ' 42 ', '42abc', '1e3', '0x1A' and 'abc'?

level: juniorimportance: must knowfreq 64%

basics

~20 s

(int) takes the leading numeric part of a string and never fails: '42', ' 42 ' and '42abc' give 42, '1e3' gives 1000, while '0x1A' and 'abc' give 0. No warning is raised for the leading-numeric or non-numeric cases.

open as a page

In PHP 8, what happens when arithmetic uses a numeric string, a leading-numeric string such as '5 boxes', a non-numeric string or an array?

level: middleimportance: should knowfreq 44%

basics

~20 s

A numeric string behaves as a number silently. A leading-numeric string like '5 boxes' uses its numeric prefix and raises E_WARNING 'A non-numeric value encountered'. A non-numeric string, an array or a plain object throws TypeError 'Unsupported operand types'.

open as a page

In PHP, how do intval(), settype() and the (array) cast differ from a plain (int) cast?

level: middleimportance: should knowfreq 24%

basics

~20 s

intval() equals (int) but takes a base, so intval('1A', 16) is 26. settype() converts a variable in place and returns true. (array) wraps a scalar in a one-element array, turns null into [] and lists an object's properties.

open as a page

A warehouse CSV import stores (int) $row['qty'], so '1,200' becomes 1 and 'n/a' becomes 0. How do you parse quantities safely in PHP 8?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Validate before converting: filter_var($raw, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]) returns an int or false, so '1,200' and 'n/a' are rejected instead of becoming 1 and 0. Normalise known formats like thousands separators explicitly, and report bad rows.

open as a page

In PHP 8.5, which cast spellings are deprecated or removed, and which new warnings can an explicit cast now raise?

level: middleimportance: nice to knowfreq 16%

basics

~20 s

PHP 8.5 deprecates the casts (integer), (boolean), (double) and (binary); (real) and (unset) were removed in 8.0. Casting an out-of-range float to int, or NAN, now warns. The new (void) cast discards a value, converting nothing.

open as a page