skip to content

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.