Cache the computation, not the query result

Caching the rows a query returned saves the database round trip and leaves the hydration, the mapping and the aggregation to happen on every request anyway.

// saves the query, keeps the work
$rows = Cache::remember("orders.{$id}", 300, function () use ($id) {
    return DB::table('order_lines')->where('order_id', $id)->get();
});
$total = $this->calculateWithDiscounts($rows);   // every request

// saves both
$total = Cache::remember("orders.{$id}.total", 300, function () use ($id) {
    return $this->calculateWithDiscounts($this->lines($id));
});

Profiling usually shows the computation dominating the query, and caching the wrong layer produces a disappointing improvement that gets blamed on the cache. The trade is invalidation: a cached total has to be cleared when a discount rule changes, which is a wider set of events than “the rows changed”. Caching a value rather than a collection also serialises to something much smaller, which matters once the cache itself is under memory pressure.