Skip to content

Commit 858cf46

Browse files
committed
Add Eloquent collection implementation
1 parent e0f291f commit 858cf46

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

src/Laravel/EloquentCollection.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel;
4+
5+
use Illuminate\Database\Query\Builder;
6+
use Illuminate\Support\Facades\Auth;
7+
use RuntimeException;
8+
use Tobyz\JsonApiServer\Context;
9+
use Tobyz\JsonApiServer\Pagination\OffsetPagination;
10+
use Tobyz\JsonApiServer\Resource\CollectionInterface;
11+
use Tobyz\JsonApiServer\Resource\Countable;
12+
use Tobyz\JsonApiServer\Resource\Listable;
13+
use Tobyz\JsonApiServer\Resource\Paginatable;
14+
15+
abstract class EloquentCollection implements CollectionInterface, Listable, Paginatable, Countable
16+
{
17+
public function resource(object $model, Context $context): ?string
18+
{
19+
foreach ($this->eloquentResources($context) as $resource) {
20+
$class = $resource->newModel($context);
21+
22+
if ($model instanceof $class) {
23+
return $resource->type();
24+
}
25+
}
26+
27+
return null;
28+
}
29+
30+
/**
31+
* @return EloquentResource[]
32+
*/
33+
public function eloquentResources(Context $context): array
34+
{
35+
return array_map(function ($type) use ($context) {
36+
$resource = $context->resource($type);
37+
38+
if (!$resource instanceof EloquentResource) {
39+
throw new RuntimeException('must be Eloquent resource');
40+
}
41+
42+
return $resource;
43+
}, $this->resources());
44+
}
45+
46+
public function endpoints(): array
47+
{
48+
return [];
49+
}
50+
51+
public function query(Context $context): object
52+
{
53+
$queries = array_map(function ($resource) use ($context) {
54+
$keyName = $resource->newModel($context)->getQualifiedKeyName();
55+
$type = $resource->type();
56+
57+
$query = $resource
58+
->query($context)
59+
->toBase()
60+
->select("$keyName as id")
61+
->selectRaw('? as type', [$type]);
62+
63+
$this->scope($query, $type);
64+
65+
return $query;
66+
}, $this->eloquentResources($context));
67+
68+
return new UnionBuilder($queries);
69+
}
70+
71+
public function scope(Builder $query, string $type): void
72+
{
73+
}
74+
75+
public function results(object $query, Context $context): array
76+
{
77+
$results = $query->get();
78+
$types = $results->groupBy('type');
79+
80+
foreach ($types as $type => $rows) {
81+
$model = $context->resource($type)->newModel($context);
82+
83+
$types[$type] = $model::findMany($rows->pluck('id'));
84+
}
85+
86+
return $results->map(fn($row) => $types[$row->type]->find($row->id))->all();
87+
}
88+
89+
public function filters(): array
90+
{
91+
return [];
92+
}
93+
94+
public function sorts(): array
95+
{
96+
return [];
97+
}
98+
99+
public function paginate(object $query, OffsetPagination $pagination): void
100+
{
101+
$query->take($pagination->limit)->skip($pagination->offset);
102+
}
103+
104+
public function count(object $query, Context $context): ?int
105+
{
106+
return $query->count();
107+
}
108+
}

src/Laravel/UnionBuilder.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel;
4+
5+
use Illuminate\Contracts\Database\Query\Builder;
6+
7+
class UnionBuilder implements Builder
8+
{
9+
private array $queryCalls = [];
10+
private array $outerQueryCalls = [];
11+
12+
public function __construct(protected array $queries)
13+
{
14+
}
15+
16+
public function count($columns = '*'): int
17+
{
18+
return $this->buildQuery()->count($columns);
19+
}
20+
21+
public function get($columns = ['*'])
22+
{
23+
return $this->buildQuery()->get($columns);
24+
}
25+
26+
protected function buildQuery()
27+
{
28+
$queries = array_map(fn($query) => clone $query, $this->queries);
29+
30+
foreach ($queries as $query) {
31+
foreach ($this->queryCalls as $call) {
32+
$call($query);
33+
}
34+
}
35+
36+
$outerQuery = array_shift($queries);
37+
38+
foreach ($queries as $query) {
39+
$outerQuery->union($query);
40+
}
41+
42+
foreach ($this->outerQueryCalls as $call) {
43+
$call($outerQuery);
44+
}
45+
46+
return $outerQuery;
47+
}
48+
49+
public function __call($method, $parameters)
50+
{
51+
$call = $this->queryCalls[] = fn($query) => $query->$method(...$parameters);
52+
53+
if ($method === 'orderBy') {
54+
$this->queryCalls[] = fn($query) => $query->addSelect($parameters[0]);
55+
}
56+
57+
if (in_array($method, ['take', 'limit', 'skip', 'offset', 'orderBy'])) {
58+
$this->outerQueryCalls[] = $call;
59+
}
60+
61+
return $this;
62+
}
63+
}

0 commit comments

Comments
 (0)