Files
lucent-laravel/src/Query/Query.php
T

291 lines
7.6 KiB
PHP
Raw Normal View History

2023-10-02 23:10:49 +03:00
<?php
namespace Lucent\Query;
use Illuminate\Database\Query\Builder;
2024-09-07 13:22:58 +03:00
use Lucent\Database\Database;
2023-10-02 23:10:49 +03:00
use Lucent\Edge\Edge;
use Lucent\Primitive\Collection;
2023-10-15 23:40:34 +03:00
use Lucent\Query\DatabaseGraph\DatabaseGraph;
2023-10-13 21:06:23 +03:00
use Lucent\Query\Filter\AndFilter;
use Lucent\Query\Filter\OrFilter;
2023-10-02 23:10:49 +03:00
use Lucent\Record\InputFormatter;
use Lucent\Record\QueryRecord;
use Lucent\Record\Record;
final class Query
{
2023-10-13 21:06:23 +03:00
/**
* @var array<AndFilter> $filters
*/
public array $filters;
2023-10-02 23:10:49 +03:00
public QueryOptions $options;
public function __construct(
2023-10-13 21:06:23 +03:00
public readonly FilterParser $filterParser,
2023-10-02 23:10:49 +03:00
public readonly InputFormatter $inputFormatter,
2023-10-15 23:40:34 +03:00
public readonly DatabaseGraph $databaseGraph,
2023-10-02 23:10:49 +03:00
)
{
$this->options = new QueryOptions();
}
public function filter(array $filterArguments): Query
{
2023-10-13 21:06:23 +03:00
$this->filters[] = new AndFilter($filterArguments);
return $this;
}
public function orFilter(array $filterArguments): Query
{
$this->filters[] = new OrFilter($filterArguments);
2023-10-02 23:10:49 +03:00
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)) {
2024-09-07 13:22:58 +03:00
$edgeRecords = Database::make()->table('records')
2023-10-02 23:10:49 +03:00
->whereIn("id", $edgesIds)
2023-10-04 13:32:30 +03:00
->whereIn("status", $this->options->status)
2023-10-02 23:10:49 +03:00
->get()->toArray();
}
$resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray();
2023-10-13 21:06:23 +03:00
// $resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges))
// ->unique(fn($edge) => $edge->source . $edge->target . $edge->field)
// ->toArray();
2023-10-02 23:10:49 +03:00
2023-12-12 14:08:39 +02:00
$formattedRecords = $this->formatRecords($resultsRecordsUnique, $resultChildrenEdges, $resultParentEdges);
2024-09-07 15:31:56 +03:00
2023-10-13 21:06:23 +03:00
$this->reset();
2023-12-12 14:08:39 +02:00
return $formattedRecords;
2023-10-02 23:10:49 +03:00
2023-10-13 21:06:23 +03:00
}
2023-10-02 23:10:49 +03:00
2023-10-13 21:06:23 +03:00
private function reset()
{
$this->options = new QueryOptions();
$this->filters = [];
2023-10-02 23:10:49 +03:00
}
2023-10-13 21:06:23 +03:00
private function formatRecords(array $records, array $edges, array $parentEdges): Graph
2023-10-02 23:10:49 +03:00
{
$queryRecords = collect($records)->map(function ($recordData) {
$record = Record::fromDB($recordData);
2024-09-07 15:31:56 +03:00
2023-10-04 13:32:30 +03:00
$record->data = $this->inputFormatter->fill($record->schema, $record->data);
2024-09-07 15:31:56 +03:00
2023-10-02 23:10:49 +03:00
$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);
2023-12-12 18:11:00 +02:00
})->sortBy("rank")->values()->toArray();
2023-10-02 23:10:49 +03:00
2023-10-13 21:06:23 +03:00
$queryParentEdges = collect($parentEdges)->map(function ($edgeData) {
2023-12-12 18:11:00 +02:00
2023-10-13 21:06:23 +03:00
return Edge::fromArray((array)$edgeData);
2023-12-12 18:11:00 +02:00
})->sortBy("rank")->values()->toArray();
2023-12-12 14:08:39 +02:00
2023-10-13 21:06:23 +03:00
2023-10-02 23:10:49 +03:00
return new Graph(
new Collection($queryRecords),
new Collection($queryEdges),
2023-10-13 21:06:23 +03:00
new Collection($queryParentEdges),
2023-12-12 14:08:39 +02:00
$this->options,
2023-10-02 23:10:49 +03:00
);
}
public function tree(): Collection
{
return $this->run()->tree();
}
private function parseFilters(Builder $query): Builder
{
2023-10-13 21:06:23 +03:00
foreach ($this->filters as $filter) {
$query = $this->filterParser->parse($query, $filter);
2023-10-02 23:10:49 +03:00
}
2023-10-04 13:32:30 +03:00
$query->whereIn("status", $this->options->status);
2023-10-02 23:10:49 +03:00
return $query;
}
2023-10-13 21:06:23 +03:00
2023-10-02 23:10:49 +03:00
private function mainQuery(): array
{
2024-09-07 13:22:58 +03:00
$query = Database::make()->table("records");
2023-10-02 23:10:49 +03:00
$query = $this->parseFilters($query);
2024-08-16 17:38:26 +03:00
$query = $this->findNotLinked($query);
2024-08-17 19:23:19 +03:00
2023-10-13 21:06:23 +03:00
if ($this->options->limit > 0) {
2023-10-08 02:12:02 +03:00
$query->limit($this->options->limit);
$query->offset($this->options->skip);
}
2023-10-02 23:10:49 +03:00
$query = $this->orderByQuery($query);
return $query->get()->map(function ($r) {
$r->isRoot = true;
return $r;
})->toArray();
}
2024-08-16 17:38:26 +03:00
private function findNotLinked(Builder $query): Builder
{
2024-08-24 17:22:40 +03:00
if (empty($this->options->notLinked)) {
2024-08-16 17:38:26 +03:00
return $query;
}
// This returns only records that have no parents
$query
->select("records.*")
->join('edges', 'records.id', '=', 'edges.target', 'left outer')
2024-08-24 17:22:40 +03:00
->whereNull("edges.target");
2024-08-16 17:38:26 +03:00
return $query;
}
2023-10-02 23:10:49 +03:00
private
function getChildren(array $ids): array
{
2023-10-15 23:40:34 +03:00
return $this->databaseGraph->getChildren($ids, $this->options);
2023-10-02 23:10:49 +03:00
}
2023-10-13 21:06:23 +03:00
private function getParents(array $ids): array
2023-10-02 23:10:49 +03:00
{
2023-10-15 23:40:34 +03:00
return $this->databaseGraph->getParents($ids, $this->options);
2023-10-02 23:10:49 +03:00
}
2023-10-04 13:32:30 +03:00
2023-10-02 23:10:49 +03:00
public
function runWithCount(): Graph
{
2024-09-07 13:22:58 +03:00
$query = Database::make()->table("records");
2023-10-02 23:10:49 +03:00
$query = $this->parseFilters($query);
2024-08-17 19:23:19 +03:00
$query = $this->findNotLinked($query);
2023-10-02 23:10:49 +03:00
$graph = $this->run();
$graph->total = $query->count();
return $graph;
}
2024-01-15 16:41:48 +02:00
public function limit(int $limit): Query
2023-10-02 23:10:49 +03:00
{
$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;
}
2023-10-13 21:06:23 +03:00
public function childrenFields(array $fields): Query
{
$this->options->childrenFields = $fields;
return $this;
}
public function parentFields(array $fields): Query
{
$this->options->parentFields = $fields;
return $this;
}
2023-10-02 23:10:49 +03:00
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;
}
2024-08-23 16:54:33 +03:00
public function onlyPublished(): Query
{
$this->options->status = ["published"];
return $this;
}
2023-10-02 23:10:49 +03:00
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;
}
2024-08-16 17:38:26 +03:00
public function notLinked(string $value): Query
{
$this->options->notLinked = $value;
return $this;
}
2023-10-02 23:10:49 +03:00
}