PHP 8.5 launches November 20th, 2025, and honestly? It's one of those releases where the changelog doesn't look massive, but every single feature is something you'll actually use.
This isn't PHP 8.0 with its JIT compiler and major type system overhaul. It's not trying to be. Instead, 8.5 focuses on the stuff that's been bugging developers for years like finally getting a proper pipe operator, fixing PHP's ancient URL handling, and making debugging less of a nightmare.
The pipe operator (|>) is the big one. If you've used Laravel collections or JavaScript's method chaining, you already know why this matters. It lets you write code that reads left-to-right, like you're telling a story, instead of nesting functions inside each other.
Here's the difference:
1// Before 2$result = array_filter( 3 array_map('strtoupper', 4 str_split( 5 htmlentities("Hello World") 6 ) 7 ), 8 fn($v) => $v != 'O' 9);10 11// After12$result = "Hello World"13 |> htmlentities(...)14 |> str_split(...)15 |> fn($x) => array_map(strtoupper(...), $x)16 |> fn($x) => array_filter($x, fn($v) => $v != 'O');
Yeah, the second one is way longer. But you can actually read it. Take some data, escape it, split it, uppercase it, filter it. Done.
The ... syntax is first-class callables from PHP 8.1, and it works brilliantly with pipes. You can build data transformation pipelines that don't require collections - perfect for processing API responses, transforming DTOs, or building complex filters.
This one's huge for anyone using readonly classes (which, let's be honest, you should be). Before 8.5, updating properties on readonly objects was painful:
1// Before PHP 8.5 2readonly class Color { 3 public function __construct( 4 public int $red, 5 public int $green, 6 public int $blue, 7 public int $alpha = 255, 8 ) {} 9 10 public function withAlpha(int $alpha): self {11 $values = get_object_vars($this);12 $values['alpha'] = $alpha;13 return new self(...$values);14 }15}
Now you just pass an array to clone():
1// PHP 8.5 2readonly class Color { 3 public function __construct( 4 public int $red, 5 public int $green, 6 public int $blue, 7 public int $alpha = 255, 8 ) {} 9 10 public function withAlpha(int $alpha): self {11 return clone($this, ['alpha' => $alpha]);12 }13}
This makes the "wither" pattern (immutable objects with with*() methods) actually pleasant to use. If you're building DTOs or value objects in Laravel, this is going to clean up a lot of boilerplate.
Two new functions: array_first() and array_last(). That's it. But you'll use them constantly.
1$users = ['Alice', 'Bob', 'Charlie']; 2$firstUser = array_first($users); // 'Alice' 3$lastUser = array_last($users); // 'Charlie' 4 5// Works with associative arrays too 6$data = ['name' => 'John', 'age' => 30, 'city' => 'London']; 7echo array_first($data); // 'John' 8echo array_last($data); // 'London' 9 10// Returns null for empty arrays11$empty = [];12var_dump(array_first($empty)); // null
Sure, these look trivial. But how many times have you written reset() and end() or done the array_values($array)[0] dance? This is cleaner and makes your code obvious.
Let's be real: parse_url() has been a mess for years. It doesn't follow modern standards, it handles edge cases weirdly, and Unicode URLs? Forget about it.
PHP 8.5 adds a proper URI extension with classes that actually follow RFC 3986 and WHATWG URL standards:
1use Uri\Rfc3986\Uri; 2 3$uri = Uri::parse('https://example.com/path?query=value'); 4$modified = $uri->withHost('newdomain.com'); 5 6// Check if URLs are equivalent 7$equivalent = $uri->isEquivalent($other_uri); 8 9// Get components10echo $uri->getScheme(); // 'https'11echo $uri->getHost(); // 'example.com'
These are immutable objects (readonly classes), so you chain modifications without worrying about side effects. They handle Unicode properly with punycode, resolve relative URLs correctly, and just... work.
If you're doing multi-tenant Laravel apps or anything with complex URL manipulation, this is going to save you headaches.
This one's huge for debugging. Fatal errors now include stack traces by default. No more staring at "Fatal error on line 8" and wondering how the hell you got there.
1// Before PHP 8.52Fatal error: Maximum execution time exceeded in script.php on line 83 4// PHP 8.55Fatal error: Maximum execution time exceeded in script.php on line 86Stack trace:7#0 script.php(12): processLargeData()8#1 script.php(20): handleRequest()9#2 {main}
The traces respect #[\SensitiveParameter] and zend.exception_ignore_args, so you won't leak passwords into logs.
Plus two new functions for runtime introspection:
1$errorHandler = get_error_handler();2$exceptionHandler = get_exception_handler();
Useful for frameworks and debugging tools that need to work with existing handlers. Laravel's exception handling could do some interesting things with this.
Quick one: php --ini=diff shows only the INI settings that differ from defaults.
1php --ini=diff2 3# Output:4# memory_limit = 256M (default: 128M)5# max_execution_time = 60 (default: 30)
Super handy when debugging config issues across environments.
Two features for apps that need proper i18n:
1// Detect RTL locales 2locale_is_right_to_left('ar-SA'); // true 3locale_is_right_to_left('en-GB'); // false 4 5// Format lists properly for different locales 6$formatter = new IntlListFormatter('en-US'); 7echo $formatter->format(['Paris', 'London', 'Tokyo']); 8// "Paris, London, and Tokyo" 9 10$formatter = new IntlListFormatter('fr-FR');11echo $formatter->format(['Paris', 'Londres', 'Tokyo']);12// "Paris, Londres et Tokyo"
Might seem niche, but if you're building SaaS for international markets, this matters.
New attribute that warns you when you ignore a function's return value:
1#[\NoDiscard] 2function getPhpVersion(): string { 3 return 'PHP 8.5'; 4} 5 6getPhpVersion(); 7// Warning: The return value of function getPhpVersion() should 8// either be used or intentionally ignored by casting it as (void) 9 10// If you actually want to ignore it:11(void) getPhpVersion(); // No warning
Great for API design where forgetting to use the return value is a common mistake. Think builder patterns, fluent interfaces, or functions that return modified state.
You can now use static closures and first-class callables in attribute parameters, constant expressions, and default values:
1final class PostsController {2 #[AccessControl(3 new Expression('request.user === post.getAuthor()'),4 )]5 public function update(Request $request, Post $post): Response {6 // ...7 }8}
Opens up some interesting metaprogramming possibilities.
curl_share_init_persistent() creates handles that survive across requests, avoiding repeated connection initialization:
1$sh = curl_share_init_persistent();2curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);3curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);4 5$ch = curl_init('https://php.net/');6curl_setopt($ch, CURLOPT_SHARE, $sh);7curl_exec($ch);
Performance boost for apps making lots of HTTP requests to the same hosts.
1$multiHandle = curl_multi_init(); 2$ch1 = curl_init('https://api.example.com/users'); 3$ch2 = curl_init('https://api.example.com/posts'); 4 5curl_multi_add_handle($multiHandle, $ch1); 6curl_multi_add_handle($multiHandle, $ch2); 7 8// New in PHP 8.5 9$handles = curl_multi_get_handles($multiHandle);10 11// Execute and process12$running = null;13do {14 curl_multi_exec($multiHandle, $running);15} while ($running > 0);16 17foreach ($handles as $handle) {18 $response = curl_multi_getcontent($handle);19 curl_multi_remove_handle($multiHandle, $handle);20}
New constant:
1echo PHP_BUILD_DATE; // 'Nov 15 2025 10:30:45'
Useful for production debugging and deployment verification.
Closure::getCurrent() - Get the currently executing closure (makes recursion easier)Dom\Element::getElementsByClassName() and insertAdjacentHTML()** - More DOM manipulation methodsgrapheme_levenshtein() - Calculate edit distance for grapheme clustersfinal keyword with constructor property promotion - You can finally use final public in constructors#[\Override] and #[\Deprecated] combined - Better attribute compositionSet a maximum that memory_limit can never exceed:
1max_memory_limit = 512M
Code can't increase memory_limit beyond this at runtime. Good for shared hosting or strict resource controls.
The usual cleanup of old stuff:
(bool), (int), (float) instead of (boolean), (integer), (double)__sleep() and __wakeup() - Use __serialize() and __unserialize() insteadcase statements - Use colons like you're supposed todisable_classes INI setting - Finally getting removedshell_exec() explicitlynull to array functions - Will throw TypeErrorsThese won't break your code yet just E_DEPRECATED warnings. PHP 9.0 (late 2026) will actually remove them.
No. Wait for 8.5.1.
The PHP team always recommends waiting for the first bug fix release. 8.5.0 drops tomorrow, so 8.5.1 should hit within a few weeks. Let the early adopters find the weird edge cases first.
When you do upgrade your Laravel apps:
If you're still on PHP 7.4 or 8.0, those versions are past EOL. No security patches. Time to upgrade.
PHP versions now get 4 years total support: 2 years active (bugs + security), then 2 years security-only. EOL dates fall on December 31st now instead of release anniversaries.
So PHP 8.5 gets active support until ~November 2027, security support until December 31, 2029. Plenty of time.
No JIT-level breakthroughs here, but the usual incremental improvements. New array functions are in C so they're fast. The URI extension is built on modern parsers. Zend Engine optimisations, better opcache, improved memory management.
Nothing earth-shattering, but staying current with PHP versions gets you these benefits for free.
Development's already started on PHP 9.0, expected late 2026. It'll be a major version with breaking changes:
Two years is plenty of time to prepare.
Some things to think about:
PHP 8.5 isn't trying to be revolutionary. It's focused on making your day-to-day coding better. The pipe operator is legitimately great. The URI stuff fixes a problem that's existed forever. Fatal error traces will save you hours of debugging.
If you're writing modern Laravel apps, this release gives you more tools to write clean, maintainable code. The language keeps getting better.
When 8.5 drops tomorrow, spin it up in your local environment. Play with the pipe operator in Tinkerwell. Try the URI classes. See where the new array functions can simplify your code. Actually using these features is the only way to appreciate them.
PHP turns 30 this year, and it's not slowing down. Active community, PHP Foundation backing, steady improvements. The language is in good hands.
Resources:
Test PHP 8.5 locally with Herd or Valet, upgrade your version, run your tests. Report any issues you find and share what you discover.
Written by
Writing and maintaining @LaravelMagazine. Host of "The Laravel Magazine Podcast". Pronouns: vi/vim.
Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.