Custom collection classes on a model

Behaviour that belongs to a set rather than to a single record — the total of a group of order lines, whether any of them is backordered — ends up as a helper function or a static method, because there is nowhere obvious to put it.

class OrderLineCollection extends Collection
{
    public function total(): Money
    {
        return $this->reduce(function ($carry, $line) {
            return $carry->add($line->subtotal());
        }, Money::zero());
    }
}

class OrderLine extends Model
{
    public function newCollection(array $models = [])
    {
        return new OrderLineCollection($models);
    }
}

Every query returning these models now returns the richer collection, including through relations, so $order->lines->total() works without anything at the call site knowing. The restraint required is to keep it to set behaviour: a method that only ever touches one element belongs on the model, and a collection class accumulating those becomes the same junk drawer it was meant to replace.