advanced php language
NoDiscard Attribute
#[\NoDiscard] tells PHP that a function or method returns a value the caller should not ignore. If code calls it as a standalone statement and discards the return value, PHP emits a warning.
This is useful for functions where ignoring the return value is probably a bug: immutable updates, validation results, parsing results, failed-operation objects, or APIs where the return value contains the important information.
The Problem It Solves
Some functions look like they change something, but actually return a changed value.
<?php
declare(strict_types=1);
function normaliseEmail(string $email): string
{
return strtolower(trim($email));
}
$email = ' ADA@EXAMPLE.COM ';
normaliseEmail($email);
echo $email . PHP_EOL;
// Prints:
// ADA@EXAMPLE.COM
The call did not update $email. It returned a new string, but the caller ignored it.
Marking A Return Value As Important
Add #[\NoDiscard] when discarding the return value would usually be wrong.
<?php
declare(strict_types=1);
#[\NoDiscard]
function normaliseEmail(string $email): string
{
return strtolower(trim($email));
}
$email = ' ADA@EXAMPLE.COM ';
$email = normaliseEmail($email);
echo $email . PHP_EOL;
// Prints:
// ada@example.com
The correct call stores or uses the return value. If the call was written as normaliseEmail($email);, PHP would warn that the return value should not be discarded.
Custom Warning Messages
You can add a message that tells the caller what they probably meant to do.
<?php
declare(strict_types=1);
#[\NoDiscard('Store the returned object; the original instance is unchanged.')]
function withStatus(array $order, string $status): array
{
$order['status'] = $status;
return $order;
}
$order = ['id' => 1001, 'status' => 'basket'];
$order = withStatus($order, 'paid');
echo $order['status'] . PHP_EOL;
// Prints:
// paid
The message should be short and actionable. It is there to help someone fix the call site quickly.
Methods On Immutable Objects
#[\NoDiscard] fits naturally on methods that return a changed copy.
<?php
declare(strict_types=1);
readonly class Money
{
public function __construct(
public int $amountCents,
public string $currency,
) {
}
#[\NoDiscard('Use the returned Money instance.')]
public function withAmountCents(int $amountCents): self
{
return new self($amountCents, $this->currency);
}
}
$price = new Money(1299, 'GBP');
$salePrice = $price->withAmountCents(999);
echo $price->amountCents . PHP_EOL;
echo $salePrice->amountCents . PHP_EOL;
// Prints:
// 1299
// 999
Ignoring withAmountCents() would leave the original price unchanged. The attribute makes that mistake visible.
Intentional Discard
Sometimes you really do want to ignore the return value. Cast the call to void to make that intention explicit.
<?php
declare(strict_types=1);
#[\NoDiscard]
function warmCache(string $key): bool
{
return $key !== '';
}
(void) warmCache('products');
echo 'cache warm attempted' . PHP_EOL;
// Prints:
// cache warm attempted
Use this sparingly. If callers usually ignore the value, the function probably should not be marked #[\NoDiscard].
When To Use It
Use #[\NoDiscard] when ignoring the result is likely to break behavior or hide a failure:
- parser functions returning a parsed value or failure result
- immutable
with*()methods - validation methods returning a result object
- functions returning a new object rather than mutating the old one
- operations where the returned status controls the next step
Do not add it everywhere. A getter, formatter, or simple calculation does not automatically need the attribute if ignoring it is harmless.
What You Should Be Able To Do
After this lesson, you should be able to mark important return values with #[\NoDiscard], write a useful warning message, use (void) for intentional discard, and choose the attribute only where it prevents a real mistake.
For junior PHP work, this matters because modern code often uses immutable objects and result objects. The attribute helps reviews catch a common bug: calling the right method but throwing away the answer.
Practice
Practice: Protect An Immutable Update
Create a small immutable object example where ignoring the returned value would be a bug.
Task
Build:
- a
readonlyOrderStateclass with astatusproperty - a
markPaid()method that returns a changed copy - a
#[\NoDiscard]attribute onmarkPaid() - a short example that stores the returned object and prints both statuses
Use strict types. Keep the expected output inside the PHP code block as printed lines or comments.
Afterward, add a short note explaining what mistake #[\NoDiscard] is protecting against.
Show solution
This solution marks the immutable update method because the original object does not change.
<?php
declare(strict_types=1);
readonly class OrderState
{
public function __construct(
public string $status,
) {
}
#[\NoDiscard('Store the returned OrderState; the original object is unchanged.')]
public function markPaid(): self
{
return new self('paid');
}
}
$basket = new OrderState('basket');
$paid = $basket->markPaid();
echo 'original: ' . $basket->status . PHP_EOL;
echo 'changed: ' . $paid->status . PHP_EOL;
// Prints:
// original: basket
// changed: paid
#[\NoDiscard] protects against writing $basket->markPaid(); and assuming $basket changed. The method returns a new object, so the caller must use that returned value.