Which PHP 7 warnings became thrown Error exceptions in PHP 8, and what typically breaks when legacy code is upgraded?
answer
- 8.0 reclassified engine warnings
- property write on null now throws
- undefined constant is an Error
- non-numeric string arithmetic: TypeError
- undefined variable is still only a warning
basics
~20 sPHP 8 throws where PHP 7 warned: property writes on null, scalars used as arrays, undefined constants, arithmetic on non-numeric strings, division by zero and invalid built-in arguments. Legacy code that carried on past those warnings now stops.
solid answer
~50 sPHP 8.0 reclassified many engine diagnostics. Now thrown as `Error`: writing a property on `null` or `false` (`Attempt to assign property "x" on null`, where PHP 7 silently created an object), writing an index on a scalar (`Cannot use a scalar value as an array`), an undefined unqualified constant (`Undefined constant "FOO"`, formerly read as the string `'FOO'`), and appending past the `PHP_INT_MAX` key. Now a `TypeError`: an array or object as an array key, unpacking a non-Traversable, `count()` on a non-countable, and arithmetic on non-numeric strings (`"abc" + 1`). Built-ins throw `TypeError`, `ValueError` or `ArgumentCountError` instead of warning and returning `null`, and `1 / 0` throws `DivisionByZeroError` where PHP 7 warned and returned `INF`. Undefined variables, array keys and properties became warnings, not errors. Upgrades break where code relied on carrying on past these.
code
php · 18 lines<?php
$cases = [
'property on null' => function () { $c = null; $c->timeout = 5; },
'scalar as array' => function () { $n = 1; $n[0] = 2; },
'bareword const' => function () { return FOO; },
'non-numeric math' => function () { return 'abc' + 1; },
'divide by zero' => function () { return 1 / 0; },
'undefined var' => function () { return $missing ?? 'default'; },
];
foreach ($cases as $name => $case) {
try {
$case();
echo "$name: no error", PHP_EOL;
} catch (Error $e) {
echo "$name: ", get_class($e), ' - ', $e->getMessage(), PHP_EOL;
}
}go deeper
Know that PHP 8 stops on many mistakes PHP 7 only warned about, such as writing a property on null or dividing by zero.
List the main reclassified cases, separate those that became Errors from notices that only became warnings, and explain leading-numeric strings.
Plan an upgrade: find the reclassified cases with tests, logs and static analysis, and add Throwable-aware logging at boundaries while fixing them.
Weigh upgrade risk across a large legacy estate, deciding how much to fix before switching versions versus catching and logging in production.
## Why PHP 8 changed so many diagnostics PHP traditionally reported misuse through **warnings** and **notices**: the script printed or logged a message and **kept running**, usually with `null`, `false` or some guessed value. That made bugs quiet: a failed step produced a bad value that surfaced much later. PHP 8.0 reclassified a large set of these into thrown `Error` objects, which stop execution at the faulty line unless caught. ## Warnings that became Errors From the PHP 8.0 migration notes: | Situation | PHP 8 result | |---|---| | writing a property on `null`, `false` or `''` | `Error`: `Attempt to assign property "x" on null` (PHP 7 auto-created an object) | | appending when key `PHP_INT_MAX` is used | `Error` | | array or object used as an array key | `TypeError` | | writing an array index on a scalar | `Error`: `Cannot use a scalar value as an array` | | unpacking a non-array, non-Traversable | `TypeError`: `Only arrays and Traversables can be unpacked` | | undefined unqualified constant | `Error`: `Undefined constant "FOO"` (PHP 7 used the string `'FOO'`) | | wrong argument count to a non-variadic built-in | `ArgumentCountError` | | `count()` on a non-countable | `TypeError` | ## Arithmetic and numeric strings - Arithmetic on a **non-numeric** string — `"abc" + 1` — throws `TypeError` (`Unsupported operand types`), where PHP 7 warned and used `0`. - A **leading-numeric** string such as `"5 apples" + 1` still works, yielding `6`, with `Warning: A non-numeric value encountered`. - Arrays, resources and plain objects as operands of arithmetic or bitwise operators throw `TypeError`. - `1 / 0` throws `DivisionByZeroError` (`Division by zero`); PHP 7 warned and returned `INF`. Modulo by zero has thrown `DivisionByZeroError` (`Modulo by zero`) since PHP 7.0. ## Built-in function arguments Built-ins now validate their arguments and throw `TypeError` for an uncoercible type and `ValueError` (new in 8.0) for an invalid value, where PHP 7 often returned `null` with a warning. Messages name the argument: `str_repeat(): Argument #2 ($times) must be greater than or equal to 0`. ## What did NOT become an Error The same release raised several **notices to warnings** only; they still do not stop execution: - reading an undefined variable; - reading an undefined array key or property; - reading a property of a non-object; - converting an array to string. So `echo $undefined;` prints a warning and continues. Teams that want these to stop execution convert warnings into exceptions with an error handler, which is a separate mechanism. ## Deprecations are not errors either Later 8.x releases added **deprecations** rather than new `Error`s for many legacy patterns. For example, passing `null` to a non-nullable scalar parameter of a built-in function, such as `strlen(null)`, has been deprecated since PHP 8.1: it emits a deprecation notice and still runs, rather than throwing `TypeError`. Deprecations signal what a future major version may turn into an error, so upgrade work usually fixes them early, but they do not stop execution today. ## What breaks on upgrade 1. **Guard-less property writes.** `$config->timeout = 5;` where `$config` is sometimes `null` used to create a `stdClass`; now it throws. 2. **Bareword constants.** Code relying on `FOO` meaning `'FOO'` now throws. 3. **Unchecked user input in arithmetic.** A form value `"abc"` in a calculation throws `TypeError`. 4. **Legacy safety nets.** `try { ... } catch (Exception $e)` does not catch any of these, because they are on the `Error` branch. 5. **Return-value checks.** Code that tested `if ($result === null)` after a bad built-in call never reaches the check; the call throws. ## Finding them before production - Run the test suite on PHP 8 and search logs for `Uncaught TypeError`, `Uncaught ValueError` and `Uncaught Error`. - Static analysis catches many cases, such as possibly-null property writes and undefined constants, before runtime. - At job and request boundaries, catch `Throwable` to log these with context while you fix them.
- Why did PHP 8 keep undefined variables as a warning instead of throwing?Reading an undefined variable was raised from notice to warning in 8.0, not to an Error, because a great deal of existing code reads possibly-unset variables and the change would have broken it wholesale. Code that wants a hard stop installs an error handler that converts warnings into exceptions.
- Does "5 apples" + 1 throw in PHP 8?No. A leading-numeric string is still used for its numeric prefix, so the result is `6`, with `Warning: A non-numeric value encountered`. Only a string with no numeric prefix, like `"apples"`, throws `TypeError`.
saying these in an interview costs you the question
- Reading an undefined variable throws an Error in PHP 8.
- Writing a property on null still creates a stdClass object in PHP 8.
- An undefined constant is treated as its own name as a string.
- Modulo by zero only started throwing DivisionByZeroError in PHP 8.
- catch (Exception $e) catches the new PHP 8 engine errors.