Your PHP 8.5 upgrade went fine, until the logs started filling up with E_DEPRECATED notices. Most are trivial fixes. A handful will require a read-through. All of them become hard errors in PHP 9.0, so it's worth an hour now.
This is a cheat sheet. Every PHP 8.5 deprecation, the exact replacement, and the Rector command to automate the scan.
PHP 8.5 Deprecations at a Glance#
Here's the full list before we dive in:
| Deprecated | Replace With |
|---|---|
(boolean) cast |
(bool) |
(integer) cast |
(int) |
(double) cast |
(float) |
(binary) cast |
(string) |
Backtick operator `cmd` |
shell_exec('cmd') |
__sleep() / __wakeup() |
__serialize() / __unserialize() |
curl_close() |
Remove it |
curl_share_close() |
Remove it |
xml_parser_free() |
Remove it |
case 'x'; semicolon |
case 'x': colon |
null as array offset |
"" empty string |
| Incrementing non-numeric strings | str_increment() |
mysqli_execute() |
mysqli_stmt_execute() |
socket_set_timeout() |
stream_set_timeout() |
$http_response_header |
http_get_last_response_headers() |
Now the detail on the ones that actually catch people off guard.
Non-Canonical Cast Names#
The most common source of deprecation notices on legacy codebases. PHP has always accepted verbose aliases for scalar casts, PHP 8.5 stops tolerating them:
// Deprecated in PHP 8.5
$active = (boolean) $value;
$count = (integer) $value;
$price = (double) $value;
$data = (binary) $value;
Switch to the canonical short forms:
// Correct, use these
$active = (bool) $value;
$count = (int) $value;
$price = (float) $value;
$data = (string) $value;
These appear most often in older codebases and generated code. Rector handles them automatically.
The Backtick Operator#
PHP's backtick syntax executes a shell command and returns the output. It's always been a thin alias for shell_exec(). PHP 8.5 deprecates the alias:
// Deprecated
$files = `ls -la /var/www`;
$output = `git log --oneline -5`;
Replace with the explicit function call:
// PHP 8.5 forward
$files = shell_exec('ls -la /var/www');
$output = shell_exec('git log --oneline -5');
The behaviour is identical, shell_exec() returns a string, or null on failure. If you need a non-null fallback, pair it with the null coalescing operator: shell_exec('cmd') ?? ''.
__sleep() and __wakeup() Magic Methods#
PHP 8.5 soft-deprecates the old serialisation magic methods in favour of the pair introduced in PHP 7.4:
// Deprecated
class User
{
public function __sleep(): array
{
// return property names to serialise
return ['id', 'email'];
}
public function __wakeup(): void
{
// reconnect resources after deserialisation
}
}
Switch to __serialize() and __unserialize():
// PHP 8.5 forward
class User
{
public function __serialize(): array
{
// return a key => value array
return ['id' => $this->id, 'email' => $this->email];
}
public function __unserialize(array $data): void
{
$this->id = $data['id'];
$this->email = $data['email'];
}
}
Note: __sleep() returned property names; __serialize() returns a key-value array. They're not drop-in replacements, read the body before renaming. If you still support PHP 7.x, implement both pairs simultaneously; PHP will prefer the newer methods when available.
curl_close(), curl_share_close(), and xml_parser_free()#
These have been no-ops since PHP 8.0, when CurlHandle, CurlShareHandle, and XMLParser became proper objects freed automatically by the garbage collector. PHP 8.5 makes the deprecation official:
// Deprecated, these do nothing in PHP 8.0+
$ch = curl_init('https://example.com');
curl_exec($ch);
curl_close($ch); // Remove this line
Remove the close calls. That's it.
Other PHP 8.5 Deprecations Worth Flagging#
Case statement semicolons. PHP accepted semicolons as terminators for years. That stops now:
// Deprecated, semicolon terminates the case
switch ($status) {
case 'active';
break;
}
// Correct, colon
switch ($status) {
case 'active':
break;
}
null as array offset. Using null as an array key or passing it to array_key_exists() is now deprecated. Replace with an empty string "".
Incrementing non-numeric strings. The behaviour of $str++ on non-numeric strings was always ambiguous. PHP 8.5 deprecates it, use str_increment() instead.
mysqli_execute(). Always an alias for mysqli_stmt_execute(). Use the canonical name.
socket_set_timeout(). Use stream_set_timeout(), they have been identical since PHP 4.
$http_response_header. This magic variable populated after file_get_contents() HTTP calls is now deprecated. Use the new http_get_last_response_headers() function instead.
Scan Your Codebase with Rector#
Don't fix these manually in a large codebase. Rector 2.x handles most of them automatically:
composer require --dev rector/rector
Create a rector.php config at the project root targeting PHP 8.5:
<?php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/tests',
])
->withPhpSets(php85: true);
Dry-run first to see what Rector would change without touching any files:
php vendor/bin/rector --dry-run
Apply the changes:
php vendor/bin/rector
Rector catches the cast renames, curl_close() removals, __sleep()/__wakeup() conversions, and the backtick operator. For the remaining items, null array offsets, case semicolons, a quick grep -rn is usually faster than writing a custom Rector rule.
Pairing this with PHPStan at level max in your Laravel app catches the type-related issues Rector won't touch. If you want these fixes committed automatically before every push, the same pattern from running Laravel Pint with Git pre-commit hooks works for any CLI tool you want to wire into your commit workflow.
Gotchas and Edge Cases#
__serialize() and __sleep() have different return types. __sleep() returned an array of property names. __serialize() returns an array of key-value pairs. Blindly renaming the method breaks serialisation, read the body first.
shell_exec() and backticks are identical in behaviour. Both return null on failure, both swallow stderr. If you need stderr, switch to proc_open() rather than shell_exec().
Rector's --dry-run is not exhaustive. Dynamic array operations using variables that might be null won't all be flagged. A manual review of your array handling is a good idea alongside the automated scan.
Nothing is removed yet. PHP 8.5 deprecates, it doesn't remove. All of these still work and just emit E_DEPRECATED. The hard removals are PHP 9.0. You have time, but cleaning up while the diff is small beats a large migration later.
Wrapping Up#
Most PHP 8.5 deprecations are mechanical find-and-replace. Run Rector, check the output, commit. The __sleep()/__wakeup() migration needs a careful read since the method contract changed.
If you're taking stock of PHP 8.5 as a whole, the PHP 8.5 pipe operator and the new array_first() and array_last() functions are the additions most worth picking up at the same time, both replace patterns that have existed unchanged in codebases for years.
FAQ#
Can I ignore these deprecations and upgrade later?
Yes, PHP 8.5 only deprecates, these still work and just emit E_DEPRECATED notices. The hard removals happen in PHP 9.0. However, cleaning up now while the diff is small is easier than a large migration later.
Will Rector catch all of these automatically?
Rector catches casts, backtick operators, curl_close/xml_parser_free removals, and __sleep/__wakeup conversions. For null array offsets and case semicolons, a quick grep is usually faster than writing a custom Rector rule.
Why did PHP deprecate non-canonical casts?
They were aliases for the short forms that existed for backward compatibility. Removing the aliases makes the language cleaner and reduces cognitive load, everyone uses (bool), never (boolean).
Is my code safe after running Rector?
Rector's dry-run gives you visibility, but it's not exhaustive. Dynamic array operations using variables that might be null won't all be flagged. A manual review alongside the automated scan is still valuable, especially for edge cases.