Get "PHP 8 in a Nuthshell" (Now with PHP 8.4)
Amit Merchant

Amit Merchant

A blog on PHP, JavaScript, and more

Everything that is coming in PHP 8.5

As every year, we will have the new version of PHP this year too, which is PHP 8.5. It’s the minor version in the PHP 8 line, and the version will be released later this year. Let’s discuss everything that has been added in PHP 8.5 so far.

The pipe operator

PHP 8.5 will introduce the pipe operator (|>), and it will allow you to write the code in a point-free style that limits the use of unnecessary intermediary variables.

So, the following code…

$value = "hello world";
$result1 = function3($value);
$result2 = function2($result1);
$result = function1($result2);

…can be rewritten using the pipe operator like this:

$value = "hello world";

$result = $value
    |> function3(...)
    |> function2(...)
    |> function1(...);

The |> operator, or “pipe”, accepts a single-parameter callable on the right and passes the left-side value to it, evaluating to the callable’s result. Pipe (|>) evaluates left to right by passing the value (or expression result) on the left as the first and only parameter to the callable on the right.

Here’s a more real-world example:

$fullName = 'Fred Flintstone';

$result = $fullName
    |> fn($x) => explode(' ', $x) // Produces an array of discrete words
    |> fn($x) => implode('_', $x) // Join those words with _
    |> strtolower(...)            // Lowercase everything
;

// $result === 'fred_flintstone'

Read more about the pipe operator in this dedicated article.

The new array methods

PHP 8.5 will introduce two new functions, array_first() and array_last() that make it easier to get the first and last elements of an array without affecting the internal pointer and without using the keys.

$array = [1, 2, 3, 4, 5];
$first = array_first($array); // 1
$last = array_last($array); // 5

$array = ['a' => 1, 'b' => 2, 'c' => 3];
$first = array_first($array); // 1
$last = array_last($array); // 3

For empty arrays, both functions return null instead of throwing an error.

$first = array_first([]); // null
$last = array_last([]); // null

Read more about these functions in this article.

The #[\NoDiscard] attribute

PHP 8.5 introduces the new #[\NoDiscard] attribute, which allows developers to indicate that the return value of a function or method should not be ignored. If a developer calls a function marked with #[\NoDiscard] and does nothing with the return value, PHP will emit a warning.

#[\NoDiscard("as the operation result is important")]
function performOperation(): int {
    // Perform some operation
    return 1; // 1 for success, 0 for failure
}   

// Calling the function without using the return value
// Warning: The return value of function performOperation() is expected to be consumed, as the operation result is important in test.php on line 10
performOperation(); 

// Calling the function and using the return value
// This will not trigger a warning
$status = performOperation();

It’s a great way to ensure that developers don’t accidentally ignore important return values, especially in cases where the return value is crucial for the operation’s success or failure.

Read more about the #[\NoDiscard] attribute in this article.

Final Property Promotion

PHP 8.5 will let you declare a property as final in the constructor property promotion syntax. This wasn’t possible before. The final keyword means that a property cannot be overridden in child classes.

class Example {
    public function __construct(
        final string $id
    ) {}
}
  • The property $id is now both promoted (created and initialized via the constructor) and final (cannot be overridden).

  • If you use final, you don’t have to specify visibility (like public or private); it defaults to public, but you can still combine final with visibility if you want.

This makes the constructor property promotion syntax consistent, and there will be less boilerplate code to write.

Read RFC

Attributes on Constants

As you may know, Attributes are a way to add metadata to code elements (like classes, methods, properties, etc.) using a special syntax. For example:

#[MyAttribute]
class MyClass {}

Now, with PHP 8.5, on top of things like classes, methods, and class constants, you can also add attributes to global (non-class) constants.

You can now add attributes to compile-time non-class constants, like:

#[MyAttribute]
const MY_CONST = 42;

One thing to keep in mind is that this does not work for constants defined with define(), only those declared with const.

You can now use ReflectionConstant::getAttributes() to read attributes on constants at runtime. Additionally, the #[\Deprecated] attribute is updated to allow targeting constants; when applied, the constant is marked with CONST_DEPRECATED.

#[Deprecated(reason: 'Use NEW_API_URL instead. This will be removed in v2.0.')]
const OLD_API_URL = 'https://old-api.example.com';

const NEW_API_URL = 'https://api.example.com';

// Usage example
function fetchData() {
    // IDEs and static analyzers can now warn about using OLD_API_URL
    $data = file_get_contents(OLD_API_URL);
    return $data;
}

Read RFC

Improved Directory class

PHP 8.5 changes PHP’s Directory class to behave like a strict, non-modifiable resource object. Essentially, the Directory class in PHP is used to represent directory handles, usually created by the dir() function. Historically, it behaved like a regular class, but this could lead to bugs and misuse (e.g., creating invalid Directory objects with new Directory()).

So, to fix this, PHP 8.5 makes the Directory class behave like a true “resource object” (sometimes called an “opaque object”).

This means:

  • Final Class: You cannot extend (inherit from) the Directory class.
  • No Direct Instantiation: You cannot create a Directory object with new Directory(). Only the dir() function can create valid instances.
  • No Cloning: You cannot clone a Directory object.
  • No Serialization: You cannot serialize or unserialize Directory objects.
  • No Dynamic Properties: You cannot add new properties to Directory objects at runtime.

Now, when you try to create a Directory object using new Directory(), it will throw an error:

$dir = new Directory(); // Throws an Error
$dir = dir('/tmp');     // Correct way to get a Directory object

Read RFC

Fatal Error Backtraces

PHP 8.5 will add automatic stack traces to PHP fatal error messages, making debugging much easier.

Today, in PHP, when a fatal error (like running out of execution time or memory) happens, the error message does not include a backtrace (a list of function calls that led to the error). Without a backtrace, it’s hard to figure out what caused the error, especially in large or complex codebases.

To mitigate this, PHP 8.5 introduces a new INI setting: fatal_error_backtraces.

When enabled (and it may be enabled by default), PHP will print a stack trace for fatal errors. The stack trace shows the sequence of function calls that led to the error, similar to what you see with exceptions.

Here’s what the backtraces looked like previously:

Fatal error: Maximum execution time of 1 second 
exceeded in example.php on line 7

With the new INI setting enabled, it will look like this:

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()
...
#11 {main}

Key things about the new error backtraces:

  • Only fatal errors (not warnings or notices) get backtraces, to avoid performance and memory issues.
  • The backtrace respects privacy settings (like SensitiveParameter attributes).
  • The backtrace is also available programmatically via error_get_last() in shutdown functions.

Read RFC

Improvements to persistent cURL share handles

PHP 8.5 improves how PHP manages persistent cURL share handles, making them safer and easier to use.

Essentially, cURL share handles let multiple cURL requests share data (like DNS cache) for efficiency. PHP recently added support for persistent cURL share handles, which can be reused across multiple PHP requests. The original design had some risks and usability issues, especially around sharing cookies and managing persistent IDs.

PHP 8.5 introduces a new curl_share_init_persistent() function that creates a persistent cURL share handle. This function is safer and more consistent than the previous curl_share_init(). You no longer need to provide a custom persistent ID; PHP manages this automatically based on the options you pass. If you call the function again with the same options, you get the same handle (it’s reused).

$options = [CURL_LOCK_DATA_DNS];
$shareHandle = curl_share_init_persistent($options);

// Use $shareHandle with CURLOPT_SHARE in your cURL requests
curl_setopt($ch, CURLOPT_SHARE, $shareHandle);

This improvement is important since it prevents accidental sharing of sensitive cookies. On top of that, the developers don’t have to manage persistent IDs or worry about handle mismatches.

Read RFC

First Class Callables in constant expressions

PHP 8.5 will allow first-class callables (FCCs) to be used in PHP constant expressions.

As you may know, first-class callables are a concise way to reference functions or static methods as callable objects using the ... syntax. For instance:

// $callable is now a callable to strlen()
$callable = strlen(...); 

// callable to MyClass::myMethod()
$callable = MyClass::myMethod(...); 

Previously, you could not use FCCs inside constant expressions (like default property values, attribute arguments, or constant definitions). PHP 8.5 changes this, allowing you to use FCCs in constant expressions.

const MY_CALLABLE = strlen(...);

#[Attr(self::myMethod(...))]
class C {}

This makes using FCCs more consistent across PHP and makes it convenient to define reusable callables as constants, use them in attributes, or as default values, making code more expressive and DRY.

Read RFC

Using Closures in Constant Expressions

PHP 8.5 will allow static closures (anonymous functions) to be used in PHP constant expressions. To understand what’s changed, let’s first understand what a constant expression is.

A constant expression in PHP is a value that must be fully determined at compile time. Examples include:

  • Default values for function parameters
  • Attribute arguments
  • Class constants
  • Property default values

Before PHP 8.5, you could not use closures (anonymous functions) in constant expressions. This has changed now in PHP 8.5. So, you can do something like this.

function my_array_filter(
    array $array,
    Closure $callback = static function ($item) { return !empty($item); }
) {
    // ...
}

Or use closures as attribute arguments, property defaults, or class constants.

There are a few constraints to this:

  • Must be static: The closure cannot use $this or capture variables from the surrounding scope (no use($foo) and no arrow functions).

  • No variable capturing: Only pure, static closures are allowed, because constant expressions can’t depend on runtime values.

  • Checked at compile time: PHP will give an error if you try to use a non-static closure or one that captures variables.

The ability to use Closures in constant expressions allows you to write cleaner code since you can provide default callbacks or validators directly in function signatures, attributes, or constants.

Apart from this, libraries can use closures in attributes for things like validation, formatting, or test case generation, like so.

final class Locale
{
    #[Validator\Custom(static function (string $languageCode): bool {
        return preg_match('/^[a-z][a-z]$/', $languageCode);
    })]
    public string $languageCode;
}

Overall, it’s a good improvement to make the code more expressive and concise.

Read RFC

The error and exception handler functions

PHP 8.5 will add get_error_handler() and get_exception_handler() functions to PHP, letting you directly retrieve the current error and exception handlers.

In PHP, you can set custom error and exception handlers using set_error_handler() and set_exception_handler(). However, there was no direct way to check what the current handler is.

Developers had to use a workaround: set a new handler, save the old one, then restore it, which is an awkward and error-prone process.

PHP 8.5 introduces two new functions:

-get_error_handler(): ?callable

-get_exception_handler(): ?callable

These functions return the currently registered error or exception handler, or null if none is set.

Here’s how you can use them.

set_error_handler(null);
get_error_handler(); // null

$handler = [$this, 'error_handler'];
set_error_handler($handler);
get_error_handler() === $handler; // true

$new_handler = $this->error_handler(...);
$old_handler = set_error_handler($new_handler);
get_error_handler() === $new_handler; // true
restore_error_handler();
get_error_handler() === $old_handler; // true

As you may guess, this makes the process of setting the handlers reliable since you get the exact handler you set, making debugging and handler management easier.

Also, frameworks and libraries can now safely check or restore handlers without side effects.

Read RFC

New INI diff option

PHP 8.5 introduces the INI diff CLI option to show all INI settings that differ from PHP’s built-in defaults.

php --ini=diff

This command lists all INI configuration directives that have been changed from their built-in default values. It’s a quick way to see which settings have been customized in your environment, which is especially useful for debugging, troubleshooting, or sharing configuration differences.

Here’s what an example output looks like.

Non-default INI settings:
memory_limit: "128M" -> "512M"
display_errors: "1" -> ""
date.timezone: "UTC" -> "Europe/Amsterdam"

Here, the left value is the default, and the right value is the current setting. Apart from this, only settings that differ from the default are shown.

This quickly lets you spot configuration changes that might affect application behavior and easily compare different environments (dev, staging, production).

This also lets you see what’s changed in containerized or automated setups.

Learn the fundamentals of PHP 8 (including 8.1, 8.2, 8.3, and 8.4), the latest version of PHP, and how to use it today with my new book PHP 8 in a Nutshell. It's a no-fluff and easy-to-digest guide to the latest features and nitty-gritty details of PHP 8. So, if you're looking for a quick and easy way to PHP 8, this is the book for you.

👋 Hi there! This is Amit, again. I write articles about all things web development. If you enjoy my work (the articles, the open-source projects, my general demeanour... anything really), consider leaving a tip & supporting the site. Your support is incredibly appreciated!

Comments?