> $rows @return list> */ public function filter(array $rows, array|\ReportFilters|null $criteria = null): array { $filters = $criteria instanceof \ReportFilters ? $criteria : \ReportFilters::fromArray($criteria ?? []); $result = []; foreach ($rows as $row) { if (!is_array($row) || !$filters->matches($row)) continue; $result[] = $row; } usort($result, static fn (array $a, array $b): int => strcmp((string)($a['changed_at'] ?? $a['created_at'] ?? ''), (string)($b['changed_at'] ?? $b['created_at'] ?? '')) ?: ((int)($a['id'] ?? 0) <=> (int)($b['id'] ?? 0))); return $result; } /** @return list> */ public function forClient(array $rows, int|string $clientId, array $criteria = []): array { if (!is_int($clientId) && (!is_string($clientId) || preg_match('/^[1-9]\d*$/', trim($clientId)) !== 1)) throw new \InvalidArgumentException('Client ID must be a positive integer.'); $criteria['client_id'] = (int)$clientId; return $this->filter($rows, $criteria); } /** @return array */ public function validateForClient(mixed $clientId, array $rows, array $criteria = []): array { $id = $this->positiveId($clientId); if ($id === null) return ['valid' => false, 'client_id' => null, 'timeline' => [], 'errors' => ['client_id' => 'Client ID must be a positive integer.']]; return ['valid' => true, 'client_id' => $id, 'timeline' => $this->forClient($rows, $id, $criteria), 'errors' => []]; } /** Controller-ready client history timeline command. */ public function timeline(array $rows, mixed $clientId, array $criteria = []): array { return $this->validateForClient($clientId, $rows, $criteria); } /** @return list> */ public function history(array $rows, array|\ReportFilters|null $criteria = null): array { return $this->filter($rows, $criteria); } /** @return list> */ public function query(array $rows, array|\ReportFilters|null $criteria = null): array { return $this->filter($rows, $criteria); } /** @return list> */ public function getHistory(array $rows, array|\ReportFilters|null $criteria = null): array { return $this->filter($rows, $criteria); } private function positiveId(mixed $value): ?int { if (is_int($value) && $value > 0) return $value; if (is_string($value) && preg_match('/^[1-9]\\d*$/', trim($value)) === 1) { $id = filter_var(trim($value), FILTER_VALIDATE_INT); return $id === false ? null : $id; } return null; } } } namespace { if (!class_exists('ClientHistoryService', false)) class_alias('App\\Domain\\Reporting\\ClientHistoryService', 'ClientHistoryService'); }