November 19th, 2025

PHP
PHP 8.5 Released: Pipes, Better URLs, and Actually Useful Debug Traces

PHP 8.5 Released: Pipes, Better URLs, and Actually Useful Debug Traces

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

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// After
12$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.

Clone With: Finally Making Readonly Classes Usable

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.

Array Functions That Should've Existed Years Ago

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 arrays
11$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.

URL Handling

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 components
10echo $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.

Fatal Errors Now Tell You What Happened

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.5
2Fatal error: Maximum execution time exceeded in script.php on line 8
3 
4// PHP 8.5
5Fatal error: Maximum execution time exceeded in script.php on line 8
6Stack 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.

CLI Trick for Config Debugging

Quick one: php --ini=diff shows only the INI settings that differ from defaults.

1php --ini=diff
2 
3# Output:
4# memory_limit = 256M (default: 128M)
5# max_execution_time = 60 (default: 30)

Super handy when debugging config issues across environments.

Internationalization

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.

Other Bits Worth Knowing

#[\NoDiscard] - Stop Forgetting Return Values

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.

Closures and Callables in Constants

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.

Persistent cURL Share Handles

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.

Better cURL Multi-Handle Management

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 process
12$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}

When Was This PHP Binary Built?

New constant:

1echo PHP_BUILD_DATE; // 'Nov 15 2025 10:30:45'

Useful for production debugging and deployment verification.

Other Small Additions

  • Closure::getCurrent() - Get the currently executing closure (makes recursion easier)
  • Dom\Element::getElementsByClassName() and insertAdjacentHTML()** - More DOM manipulation methods
  • grapheme_levenshtein() - Calculate edit distance for grapheme clusters
  • final keyword with constructor property promotion - You can finally use final public in constructors
  • #[\Override] and #[\Deprecated] combined - Better attribute composition

Hard Memory Limit Ceiling

Set 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.

What's Getting Deprecated

The usual cleanup of old stuff:

  • MHASH_ constants* - Use the hash extension instead
  • Old type cast names - Use (bool), (int), (float) instead of (boolean), (integer), (double)
  • Non-string output handler returns - Output handlers should return strings or null
  • __sleep() and __wakeup() - Use __serialize() and __unserialize() instead
  • Semicolons after case statements - Use colons like you're supposed to
  • disable_classes INI setting - Finally getting removed
  • Using backticks for shell commands - Use shell_exec() explicitly
  • Passing null to array functions - Will throw TypeErrors

These won't break your code yet just E_DEPRECATED warnings. PHP 9.0 (late 2026) will actually remove them.

Should You Upgrade Right Away?

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:

  1. Test in staging first - or your CI pipeline
  2. Run PHPStan/Psalm - Catch potential issues before runtime
  3. Check your dependencies - Make sure packages support 8.5
  4. Watch for deprecation notices - Set error reporting to E_ALL in dev

If you're still on PHP 7.4 or 8.0, those versions are past EOL. No security patches. Time to upgrade.

Support Timeline

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.

Performance

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.

What's Next: PHP 9.0

Development's already started on PHP 9.0, expected late 2026. It'll be a major version with breaking changes:

  • Stricter types everywhere
  • Deprecated features actually get removed
  • Warnings becoming exceptions in some cases
  • More type system improvements

Two years is plenty of time to prepare.

For Laravel Devs

Some things to think about:

  • Pipe operator could inspire new helpers and fluent APIs
  • Better URL handling fits Laravel's routing perfectly
  • Improved debugging works great with Laravel's error pages
  • i18n features complement Laravel's localisation

Bottom Line

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.

Statamic Ninja

Comments

Marian Pop

PHP / Laravel Developer. Writing and maintaining @LaravelMagazine. Host of "The Laravel Magazine Podcast". Pronouns: vi/vim.

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.