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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 readonly OrderState class with a status property
  • a markPaid() method that returns a changed copy
  • a #[\NoDiscard] attribute on markPaid()
  • 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 example
<?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.