$filters */ public array $filters; public QueryOptions $options; public function __construct( public readonly FilterParser $filterParser, public readonly InputFormatter $inputFormatter, public readonly DatabaseGraph $databaseGraph, ) { $this->options = new QueryOptions(); } public function filter(array $filterArguments): Query { $this->filters[] = new AndFilter($filterArguments); return $this; } public function orFilter(array $filterArguments): Query { $this->filters[] = new OrFilter($filterArguments); return $this; } public function run(): Graph { $resultsRecords = $this->mainQuery(); $ids = array_map(function ($rec) { return $rec->id; }, $resultsRecords); $resultChildrenEdgesTargetIds = []; $resultChildrenEdges = []; if ($this->options->childrenDepth > 0 && !empty($ids)) { $resultChildrenEdges = $this->getChildren($ids); $resultChildrenEdgesTargetIds = array_map(fn($e) => $e->target, $resultChildrenEdges); } $resultParentSourceTargetIds = []; $resultParentEdges = []; if ($this->options->parentsDepth > 0 && !empty($ids)) { $resultParentEdges = $this->getParents($ids); $resultParentSourceTargetIds = array_map(fn($e) => $e->source, $resultParentEdges); } $edgesIds = collect($resultParentSourceTargetIds)->merge($resultChildrenEdgesTargetIds)->unique()->values()->toArray(); $edgeRecords = []; if (!empty($edgesIds)) { $edgeRecords = DB::table('records') ->whereIn("id", $edgesIds) ->whereIn("status", $this->options->status) ->get()->toArray(); } $resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray(); // $resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges)) // ->unique(fn($edge) => $edge->source . $edge->target . $edge->field) // ->toArray(); $formattedRecords = $this->formatRecords($resultsRecordsUnique, $resultChildrenEdges, $resultParentEdges); $this->reset(); return $formattedRecords; } private function reset() { $this->options = new QueryOptions(); $this->filters = []; } private function formatRecords(array $records, array $edges, array $parentEdges): Graph { $queryRecords = collect($records)->map(function ($recordData) { $record = Record::fromDB($recordData); $record->data = $this->inputFormatter->fill($record->schema, $record->data); $queryRecord = QueryRecord::fromRecord($record); $queryRecord->isRoot = data_get($recordData, "isRoot") === true; return $queryRecord; })->toArray(); $queryEdges = collect($edges)->map(function ($edgeData) { return Edge::fromArray((array)$edgeData); })->sortBy(['depth', 'asc'], ['rank', 'desc'])->values()->toArray(); $queryParentEdges = collect($parentEdges)->map(function ($edgeData) { return Edge::fromArray((array)$edgeData); })->sortBy(['depth', 'asc'], ['rank', 'desc'])->values()->toArray(); return new Graph( new Collection($queryRecords), new Collection($queryEdges), new Collection($queryParentEdges), $this->options, ); } public function tree(): Collection { return $this->run()->tree(); } private function parseFilters(Builder $query): Builder { foreach ($this->filters as $filter) { $query = $this->filterParser->parse($query, $filter); } $query->whereIn("status", $this->options->status); return $query; } private function mainQuery(): array { $query = DB::table("records"); $query = $this->parseFilters($query); if ($this->options->limit > 0) { $query->limit($this->options->limit); $query->offset($this->options->skip); } $query = $this->orderByQuery($query); return $query->get()->map(function ($r) { $r->isRoot = true; return $r; })->toArray(); } private function getChildren(array $ids): array { return $this->databaseGraph->getChildren($ids, $this->options); } private function getParents(array $ids): array { return $this->databaseGraph->getParents($ids, $this->options); } public function runWithCount(): Graph { $query = DB::table("records"); $query = $this->parseFilters($query); $graph = $this->run(); $graph->total = $query->count(); return $graph; } public function limit(int $limit): Query { $this->options->limit = $limit; return $this; } public function skip(int $skip): Query { $this->options->skip = $skip; return $this; } public function childrenDepth(int $depth): Query { $this->options->childrenDepth = $depth; return $this; } public function childrenLimit(int $limit): Query { $this->options->childrenLimit = $limit; return $this; } public function childrenFields(array $fields): Query { $this->options->childrenFields = $fields; return $this; } public function parentFields(array $fields): Query { $this->options->parentFields = $fields; return $this; } public function parentsDepth(int $depth): Query { $this->options->parentsDepth = $depth; return $this; } public function parentsLimit(int $limit): Query { $this->options->parentsLimit = $limit; return $this; } public function sort(string $sort): Query { $this->options->sort[] = $sort; return $this; } public function status(array $status): Query { $this->options->status = $status; return $this; } public function orderByQuery(Builder $query): Builder { foreach ($this->options->sort as $item) { $field = str_replace(".", "->", ltrim($item, '-')); $dir = str_starts_with($item, '-') ? "desc" : "asc"; if ($field) { $query->orderBy($field, $dir); } } return $query; } }