QueryBuilderAbstract::applySorts()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiChef\RequestToEloquent;
6
7
use ApiChef\RequestQueryHelper\QueryParamBag;
8
use ApiChef\RequestQueryHelper\SortField;
9
use ApiChef\RequestQueryHelper\Sorts;
10
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
11
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
12
use Illuminate\Database\Query\Builder as QueryBuilder;
13
use Illuminate\Support\Arr;
14
use Illuminate\Support\Str;
15
use RuntimeException;
16
17
abstract class QueryBuilderAbstract
18
{
19
    use InteractWithQueryString;
0 ignored issues
show
introduced by
The trait ApiChef\RequestToEloquent\InteractWithQueryString requires some properties which are not provided by ApiChef\RequestToEloquent\QueryBuilderAbstract: $sorts, $paginationParams, $filters, $fields, $includes
Loading history...
20
21
    protected ?int $defaultPageSize = null;
22
    protected array $availableIncludes = [];
23
    protected array $availableFilters = [];
24
    protected array $availableSorts = [];
25
    private array $allowedIncludes = [];
26
    private ?array $allPossibleAvailableIncludes = null;
27
28
    /**
29
     * Initialise the query.
30
     *
31
     * @return EloquentBuilder|QueryBuilder
32
     */
33
    abstract protected function init();
34
35
    protected function includesMap(): array
36
    {
37
        return [];
38
    }
39
40
    protected function filtersMap(): array
41
    {
42
        return [];
43
    }
44
45
    protected function sortsMap(): array
46
    {
47
        return [];
48
    }
49
50
    public function parseAllowedIncludes(array $allowedIncludes): self
51
    {
52
        $this->allowedIncludes = $this->getPossibleRelationshipCombinations($allowedIncludes);
53
54
        return $this;
55 57
    }
56
57 57
    private function getPossibleRelationshipCombinations(array $relations): array
58 57
    {
59 57
        $combinations = [];
60 57
61 57
        foreach ($relations as $relation) {
62 57
            $combination = null;
63 57
            foreach (explode('.', $relation) as $part) {
64 57
                $combination .= is_null($combination) ? $part : ".{$part}";
65
                if (! in_array($combination, $combinations)) {
66
                    array_push($combinations, $combination);
67
                }
68
            }
69
        }
70
71
        return $combinations;
72
    }
73
74
    /**
75
     * @return EloquentBuilder|QueryBuilder
76
     */
77
    private function query()
78
    {
79
        $query = $this->init();
80
81
        if (! empty($this->allowedIncludes) && $this->includes()->filled()) {
82
            $this->loadIncludes($query, $this->includes());
83
        }
84
85
        if ($this->filters()->filled()) {
86
            $this->applyFilters($query, $this->filters());
87
        }
88
89 21
        if ($this->sorts()->getFields()->isNotEmpty()) {
90
            $this->applySorts($query, $this->sorts());
91 21
        }
92
93 21
        return $query;
94
    }
95
96 21
    public function get(array $columns = ['*'])
97
    {
98 21
        if ($this->shouldPaginate()) {
99
            return $this->paginate($columns);
100 21
        }
101 21
102 21
        return $this->query()->get($columns);
103 21
    }
104 21
105 21
    public function paginateWithTotal(array $columns = ['*']): LengthAwarePaginator
106
    {
107
        return $this->paginate($columns, true);
108
    }
109
110 21
    private function paginate(array $columns, bool $withTotalCount = false)
111
    {
112
        $paginate = $withTotalCount ? 'paginate' : 'simplePaginate';
113
114
        return $this->query()->{$paginate}(
115
            $this->paginationParams()->perPage($this->defaultPageSize),
116
            $columns,
117
            $this->paginationParams()->pageName(),
118 57
            $this->paginationParams()->page(1)
119
        )->withQueryString();
120 57
    }
121 21
122
    private function shouldPaginate(): bool
123
    {
124 54
        return $this->defaultPageSize !== null || $this->paginationParams()->filled();
125 12
    }
126
127
    public function first(array $columns = ['*'])
128 51
    {
129 12
        return $this->query()->first($columns);
130
    }
131
132 48
    private function loadIncludes($query, QueryParamBag $includes): void
133
    {
134
        $includes->each(function ($params, $relation) use ($query) {
135
            if ($this->isAllowedToInclude($relation)) {
136
                $this->loadRelation($query, $relation, $params);
137
            }
138
        });
139
    }
140
141 33
    private function isAllowedToInclude($relation): bool
142
    {
143 33
        return in_array($relation, $this->allowedIncludes);
144 6
    }
145 6
146 4
    private function loadRelation($query, $relation, $params): void
147 6
    {
148 6
        if ($relationAlias = Arr::get($this->includesMap(), $relation)) {
149
            $query->with($relationAlias);
150
151
            return;
152 27
        }
153
154
        $methodName = 'include'.Str::studly(str_replace('.', 'With', $relation));
155
        if (method_exists($this, $methodName)) {
156
            $this->{$methodName}($query, $params);
157
158
            return;
159
        }
160
161 21
        if (is_null($this->allPossibleAvailableIncludes)) {
162
            $this->allPossibleAvailableIncludes = $this->getPossibleRelationshipCombinations($this->availableIncludes);
163 21
        }
164
165
        if (in_array($relation, $this->allPossibleAvailableIncludes)) {
0 ignored issues
show
Bug introduced by
It seems like $this->allPossibleAvailableIncludes can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
        if (in_array($relation, /** @scrutinizer ignore-type */ $this->allPossibleAvailableIncludes)) {
Loading history...
166 21
            $query->with($relation);
167
168
            return;
169 21
        }
170 21
171
        throw new RuntimeException("Trying to include non existing relationship {$relation}");
172 21
    }
173 18
174
    private function applyFilters($query, QueryParamBag $filters): void
175 21
    {
176
        $filters->each(function ($params, $scope) use ($query) {
177 21
            $this->applyFilter($query, $scope, $params);
178
        });
179
    }
180 21
181
    private function applyFilter($query, $scope, $params): void
182 21
    {
183 3
        if ($filterAlias = Arr::get($this->filtersMap(), $scope)) {
184
            $query->{$filterAlias}($params);
185 3
186
            return;
187
        }
188 18
189 18
        $methodName = 'filterBy'.Str::studly($scope);
190 6
191
        if (method_exists($this, $methodName)) {
192 6
            $this->{$methodName}($query, $params);
193
194
            return;
195 12
        }
196 12
197
        if (in_array($scope, $this->availableFilters)) {
198
            $query->{$scope}($params);
199 12
200 9
            return;
201
        }
202 9
203
        throw new RuntimeException("Trying to filter by non existing filter {$scope}");
204
    }
205 3
206
    private function applySorts($query, Sorts $sorts): void
207
    {
208 12
        $sorts->getFields()->each(function (SortField $sortField) use ($query) {
209
            $this->applySort($query, $sortField->getField(), $sortField->getDirection(), $sortField->getParams());
210
        });
211 12
    }
212 12
213 9
    private function applySort($query, string $field, string $direction, array $param): void
214
    {
215 12
        if ($sortAlias = Arr::get($this->sortsMap(), $field)) {
216
            $query->orderBy($sortAlias, $direction);
217 12
218 3
            return;
219
        }
220 3
221
        $methodName = 'sortBy'.Str::studly($field);
222
223 9
        if (method_exists($this, $methodName)) {
224
            $this->{$methodName}($query, $direction, $param);
225 9
226 3
            return;
227
        }
228 3
229
        if (in_array($field, $this->availableSorts)) {
230
            $query->orderBy($field, $direction);
231 6
232 3
            return;
233
        }
234 3
235
        throw new RuntimeException("Trying to sort by non existing field {$field}");
236
    }
237
}
238