with(['detail','variants'])->find($id)), so a * Dynamic Data field or a future atom consumes an identical shape whether it reached a * product via ProductContext::find() (single) or ProductQueryAdapter::query() * (collection). * * This class never fatals: an unusable/unavailable FluentCart install, an unrecognized * top-level filter key, or an empty resultset all resolve through * ProductQueryAdapter::query() to ProductQueryResult::empty(), never a partially * constructed or null result. */ final class ProductQueryResult { /** @var object[] */ public array $products; public int $found; public int $perPage; public int $currentPage; public int $maxPages; /** @param object[] $products */ public function __construct(array $products, int $found, int $perPage, int $currentPage, int $maxPages) { $this->products = $products; $this->found = max(0, $found); $this->perPage = max(0, $perPage); $this->currentPage = max(1, $currentPage); $this->maxPages = max(0, $maxPages); } public static function empty(int $perPage = 0, int $currentPage = 1): self { return new self([], 0, $perPage, $currentPage, 0); } }