The decorator that adds caching without touching the class

Putting a cache inside a repository mixes two concerns and makes the uncached version untestable, and the alternative is a second implementation of the same interface.

final class CachedOrders implements Orders
{
    private $inner;
    private $cache;

    public function __construct(Orders $inner, CacheInterface $cache)
    {
        $this->inner = $inner;
        $this->cache = $cache;
    }

    public function ofCustomer(CustomerId $id): OrderCollection
    {
        $key = 'orders.customer.' . $id->value();

        return $this->cache->remember($key, 300, function () use ($id) {
            return $this->inner->ofCustomer($id);
        });
    }
}

// wired in the container; nothing else in the application knows

The decorated class stays free of caching concerns and remains testable on its own, and the decorator is testable with a fake inner. Swapping it out per environment is a container binding rather than a conditional. The cost is one class per decorated interface and a container definition that is no longer autowired, which is a fair trade for anything cached in more than one place.