The pipe operator, property hooks, and #[NoDiscard] in PHP 8.5 help you write clearer, safer code—with less nesting and less boilerplate.
PHP isn’t “old”—it’s evolving. With PHP 8.5 (released November 2025), you get language features that make everyday code easier to read and maintain. Here are three that actually change how you write: the pipe operator, property hooks, and the #[NoDiscard] attribute.
|>)The problem: Deeply nested function calls are hard to read. Who wants to count brackets?
// Nested: inside-out, easy to misread$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($title))));The fix: The pipe operator (|>) passes the value on the left into the callable on the right. You read it top to bottom, like a pipeline.
$title = ' PHP 8.5 Released ';
$slug = $title |> trim(...) |> (fn(string $s) => str_replace(' ', '-', $s)) |> (fn(string $s) => str_replace('.', '', $s)) |> strtolower(...);// $slug === 'php-85-released'For built-in functions like trim and strtolower, use ... so PHP treats them as single-argument callables. For custom logic, use an arrow function. Each step takes one input and returns the next value—no nesting.
Another example: normalizing user input before validation.
$raw = " JOHN@EXAMPLE.COM ";
$email = $raw |> trim(...) |> strtolower(...);// Ready for validate($email)The problem: You end up with boring getters and setters just to validate or transform a value. Lots of boilerplate for one field.
The fix: Property hooks (PHP 8.4, part of the 8.5 toolset) let you define get and set behavior directly on the property. No separate methods.
Example: validated email
class User{ public string $email { set { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Invalid email'); } $this->email = strtolower($value); } }}
$user = new User();$user->email = 'Jane@Example.COM'; // Stored as 'jane@example.com'$user->email = 'not-an-email'; // Throws InvalidArgumentExceptionExample: virtual (computed) property
When the value isn’t stored but derived, use a virtual property with only a get hook:
class Rectangle{ public function __construct( public int $height, public int $width, ) {}
public int $area { get => $this->height * $this->width; }}
$r = new Rectangle(4, 5);echo $r->area; // 20Shorthand: For a simple transform in set, you can use an arrow expression:
public string $name { set => strtolower($value);}You keep a single, clear property while moving validation and formatting into the declaration. Fewer lines, same behavior.
#[NoDiscard] attributeThe problem: Some functions communicate success or failure via their return value. If the caller ignores it, bugs can hide—e.g. a failed lock or a failed DateTimeImmutable::modify() that you never check.
The fix: The Marking return values as important RFC adds the #[\NoDiscard] attribute. If the return value is ignored, PHP emits a warning (so you don’t silently drop important results).
Using it in your own code:
#[\NoDiscard]public function generateToken(): string{ $token = bin2hex(random_bytes(32)); $this->storeToken($token); return $token;}
// Later:$this->generateToken(); // Warning: return value of generateToken() must be used$token = $this->generateToken(); // OKOptional message: You can explain why the value matters:
#[\NoDiscard('Token must be shown to the user or stored.')]public function generateToken(): string { ... }When you intentionally ignore the return value: Cast to (void) to silence the warning:
(void) $this->generateToken(); // Explicit: "I know I'm discarding this."PHP 8.5 also applies #[\NoDiscard] to several built-ins (e.g. some DateTimeImmutable::set* methods and flock()), so ignoring their return values will trigger a warning and nudge you toward safer code.
| Feature | What it does | Use it when |
|---|---|---|
| Pipe operator | Chains operations left-to-right | Transforming data step by step |
| Property hooks | get/set logic on the property | Validation, normalization, computed values |
| NoDiscard | Warns when return value is ignored | APIs that signal failure via return |
PHP 8.5 is about writing clearer, more predictable code—less nesting, less boilerplate, and fewer silent mistakes. If you’re still on 8.4 or earlier, these three features are good reasons to plan an upgrade.
References
No comments yet
Loading comments...