RSSAmplifier

stitcher.io · Nov 20, 2025

What's new in PHP 8.5

0
Sign in to vote or save

stitcher.io

What's new in PHP 8.5

Written on 2025-11-20

PHP 8.5 was released on November 20, 2025. It includes the pipe operator, clone with, a new URI parser, and more.


The pipe operator

PHP 8.5 introduces the new pipe operator that makes chaining output from one function to another a lot easier. Instead of deeply nested function calls like this:

$input = ' Some kind of string. ';
$output = strtolower(
    str_replace(['.', '/', '…'], '',
        str_replace(' ', '-',
            trim($input)
        )
    )
);

You can now write this:

$output = $input
    |> trim(...)
    |> (fn (string $string) => str_replace(' ', '-', $string))
    |> (fn (string $string) => str_replace(['.', '/', '…'], '', $string))
    |> strtolower(...);

I've done a deep-dive into this new operator, and you can read about it here.


Clone with

There's now a way to assign new values to cloned objects while cloning them:

final class Book
{
    public function __construct(
        public string $title,
        public string $description,
    ) {}
    public function withTitle(string $title): self
    {
        return clone($this, [
            'title' => $title,
        ]);
    }
}

I think this is a great feature. The only thing I find unfortunate is that it doesn't work when cloning readonly properties from the outside (which I think is a common use case). To do so, you have to specifically reset the propery's write access to public(set). I explained the problem here.


(void) cast and #[NoDiscard]

You can now mark a function with the #[NoDiscard] attribute, indicating that its return value must be used. If nothing happens with that return value, a warning will be triggered.

#[NoDiscard("you must use this return value, it's very important.")]
function foo(): string {
    return 'hi';
}
foo();
$string = foo();

The warning can still be surpressed by using the new (void) cast:

(void) foo();

Closure improvements

Closures and first-class callables can now be used in constant expressions. In practice this means you'll be able to define closures in attributes, which is an incredible new feature:

#[SkipDiscovery(static function (Container $container): bool {
    return ! $container->get(Application::class) instanceof ConsoleApplication;
})]
final class BlogPostEventHandlers
{  }

Note that these kinds of closures must always be explicitly marked as static, since they aren't attached to a $this scope. They also cannot access variables from the outside scope with use.


Backtraces for fatal errors

A small but awesome change: fatal errors will now include backtraces.

Fatal error: Maximum execution time of 1 second exceeded in example.php on line 6
Stack trace:
#0 example.php(6): usleep(100000)
#1 example.php(7): recurse()
#2 example.php(7): recurse()
#3 example.php(7): recurse()
#4 example.php(7): recurse()
#5 example.php(7): recurse()
#6 example.php(7): recurse()
#7 example.php(7): recurse()
#8 example.php(7): recurse()
#9 example.php(7): recurse()
#10 example.php(10): recurse()
#11 {main}

Added arrayfirst() and arraylast()

Perhaps a bit overdue (array_key_first() and array_key_last() were added in PHP 7.3), but we finally get built-in functions to get the first and last elements from arrays! So instead of writing this:

$first = $array[array_key_first($array)] ?? null;

You can now write this:

$first = array_first($array);

URI parsing

There's a brand new URI implemention that makes working with URIs a lot easier:

use Uri\Rfc3986\Uri;
$uri = new Uri('https://tempestphp.com/2.x/getting-started/introduction');
$uri->getHost();
$uri->getScheme();
$uri->getPort();

The #[DelayedTargetValidation] attribute

Some built-in attributes (like #[Override]) are validated at compile-time rather than at runtime when being called via reflection. The #[DelayedTargetValidation] allows you to postpone that validation to a runtime:

class Child extends Base
{
	#[DelayedTargetValidation]
	#[Override]
	public const NAME = 'Child';
}

This attribute is added to manage backwards compatibility issues. You can read a concrete example here.


Smaller changes


Deprecations and breaking changes


Those are the features and changes that stand out for PHP 8.5; you can find the whole list of everything that's changed over here.

What are your thoughts about PHP 8.5? You can leave them in the comments below!

Read the original on stitcher.io

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.