Skip to content
All articles
PHP & Modernization

PHP 7 to 8: where it breaks and how to know in advance

Most PHP upgrade failures do not come from the framework. They come from custom code and old dependencies.

G Unit SolutionsOctober 7, 20268 min read

The jump from PHP 7 to 8 is not a single update but several. Between PHP 7.4 and 8.3 lie four minor versions, each with changes to types, error behavior and extensions. Most applications do not break at startup. They break in places that worked quietly for years.

This article covers the typical breakage points and how to find them before the update.

Types get stricter

PHP 8 checks types more closely, including for internal functions. Passing null to a parameter that does not allow null produces a deprecation warning from PHP 8.1 onward. Examples include strpos(), strlen(), trim() and htmlspecialchars() called with null as an argument. In PHP 7, null was silently converted to an empty string.

Internal functions also throw TypeError more often instead of returning null or false. Code that checks the return value falls through. The same change affects union types and return types in your own signatures. What PHP 7 tolerated is rejected at runtime.

A related issue concerns default values. Signatures such as function f(string $x = null) were common for years. If you move directly to PHP 8.4 or later, they are marked as deprecated. Check those places when you touch the signatures anyway.

Warnings that become errors

A missing array key produced a notice in PHP 7. In PHP 8.0 it became a warning: "Undefined array key". Reading a property from a null value produces a similar warning.

A warning on its own is not fatal. Many frameworks and error handlers convert warnings into exceptions, such as the Symfony ErrorHandler in debug mode. A warning that was ignored for years then stops a request. In production the visible effect is often log noise that buries real failures.

Review how your application treats E_WARNING and E_DEPRECATED. If an error handler turns them into exceptions, that behavior belongs before the update, not after.

Strings and numbers

PHP 8 changed comparisons between strings and numbers. 0 == "foo" returned true in PHP 7, because the non-numeric string was converted to 0. In PHP 8 the number is converted to a string instead, so the result is false. 0 == "" is no longer true either. Comparing "0" == "foo" was already false in PHP 7 and stays false, because two strings are compared as strings in both versions.

Comparisons like these are common in grown code: status checks, discount logic, voucher codes, filters from forms. Look for loose comparisons with == in paths where money or permissions are involved, and replace them with ===. No special tool is required to start. A targeted search is enough.

PHP 8 also provides str_contains(), str_starts_with() and str_ends_with(). They replace many strpos() comparisons and are easier to read. When reworking critical paths, both changes can be combined.

Errors and exceptions

PHP 8 throws ValueError and TypeError more often for invalid arguments. A negative value in str_repeat(), an invalid mode in fopen() or a malformed DateTime call now raises an exception instead of failing silently.

Inheritance matters here. TypeError and ValueError extend Error, not Exception. A catch (Exception $e) block does not catch them. Check whether your central error handling catches Throwable. Otherwise an invalid input becomes a blank page.

Removed functions and extensions

PHP 8.0 removed functions that still appear in older code: each(), create_function(), money_format(), get_magic_quotes_gpc() and the FILTER_SANITIZE_MAGIC_QUOTES constant. The XML-RPC extension moved out of the core into PECL.

Older jumps still matter. mcrypt disappeared in PHP 7.2 and the mysql_* functions in PHP 7.0. If your application started on PHP 7.0 and those calls were never replaced, it breaks before PHP 8 is even involved.

More deprecations arrive with each minor version: strftime() and the mhash family from 8.1, utf8_encode() and utf8_decode() from 8.2, dynamic properties from 8.2. A search through the code, combined with the migration notes of the target version, produces the list of hits.

Dependencies set the lower bound

Before discussing code, settle the versions. Composer packages and frameworks determine which PHP version is possible at all.

Laravel requires PHP 8.0 in version 9, PHP 8.1 in version 10 and PHP 8.2 in version 11. Symfony 6.4 requires PHP 8.1. A package that needs ext-mcrypt or ext-xmlrpc blocks the update regardless of your own code.

Useful commands are composer check-platform-reqs against the target environment, composer why-not php 8.2 for blocking packages and composer audit for known vulnerabilities. With config.platform.php in composer.json you can test dependency resolution against the target version before installing it. Also check the CI configuration: a pipeline that still installs PHP 7.4 does not test the target version.

Finding risk in advance

Static analysis is the cheapest way to find breakage. PHPStan and Psalm check code against a configured target version. In phpstan.neon, set phpVersion to the target and run the analysis over the critical directories. A baseline records the initial findings so new problems stay visible.

Rector can also show which automatic changes an upgrade set would make. A run with --dry-run produces a diff without touching files. It is no substitute for tests, but a useful overview.

If a test suite exists, run it on the old version before the update. That is the only way to know whether a failure after the update comes from the code or from the environment.

Test on a staging environment with production-like data. Same PHP version, same extensions, same configuration. An anonymized database dump surfaces cases that synthetic test data never creates.

Define an error budget for the first days after the update. For example: no new fatal errors and no more warnings than before.

Invoice rounding, tax rates, discounts, payment callbacks and refunds are the places where a type error causes real damage. Those paths need tests before the version changes.

Conclusion

The jump to PHP 8 rarely breaks the home page. It breaks special cases nobody remembers anymore. Checking the target version, the dependencies and the critical paths beforehand finds most problems while they are still cheap to fix.

The right sequence for a grown application is described in PHP modernization. Why postponement makes things worse is covered in signs of technical debt. And if a rewrite is already on the table, the comparison modernize or rewrite helps.