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.