Passed
Push — master ( 8e3181...7b7cc4 )
by Milroy
02:25
created

QueryBuilderAbstract::applyFilter()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 11
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 23
ccs 12
cts 12
cp 1
crap 4
rs 9.9
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiChef\RequestToEloquent;
6
7
use ApiChef\RequestQueryHelper\Fields;
8
use ApiChef\RequestQueryHelper\QueryParamBag;
9
use ApiChef\RequestQueryHelper\SortField;
10
use ApiChef\RequestQueryHelper\Sorts;
11
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
12
use Illuminate\Database\Eloquent\Collection;
13
use Illuminate\Database\Eloquent\Model;
14
use Illuminate\Database\Query\Builder as QueryBuilder;
15
use Illuminate\Http\Request;
16
use Illuminate\Support\Arr;
17
use Illuminate\Support\Str;
18
19
abstract class QueryBuilderAbstract
20
{
21
    /** @var Request $request */
22
    private $request;
23
24
    /** @var Fields $fields */
25
    protected $fields;
26
27
    /** @var QueryParamBag $includes */
28
    protected $includes;
29
30
    /** @var QueryParamBag $filters */
31
    protected $filters;
32
33
    /** @var Sorts $sorts */
34
    protected $sorts;
35
36
    /** @var EloquentBuilder|QueryBuilder $request */
37
    private $query;
38
39
    private $allowedIncludes = [];
40
41
    private $allPossibleAvailableIncludes = null;
42
43
    protected $availableIncludes = [];
44
45
    protected $availableFilters = [];
46
47
    protected $availableSorts = [];
48
49 51
    public function __construct(Request $request)
50
    {
51 51
        $this->request = $request;
52 51
        $this->fields = $request->fields();
53 51
        $this->includes = $request->includes();
54 51
        $this->filters = $request->filters();
55 51
        $this->sorts = $request->sorts();
56 51
        $this->query = $this->init();
57 51
    }
58
59
    /**
60
     * Initialise the query.
61
     *
62
     * @return EloquentBuilder|QueryBuilder
63
     */
64
    abstract protected function init();
65
66
    protected function includesMap(): array
67
    {
68
        return [];
69
    }
70
71
    protected function filtersMap(): array
72
    {
73
        return [];
74
    }
75
76
    protected function sortsMap(): array
77
    {
78
        return [];
79
    }
80
81 21
    public function parseAllowedIncludes(array $allowedIncludes): self
82
    {
83 21
        $this->allowedIncludes = $this->getPossibleRelationshipCombinations($allowedIncludes);
84
85 21
        return $this;
86
    }
87
88 21
    private function getPossibleRelationshipCombinations(array $relations)
89
    {
90 21
        $combinations = [];
91
92 21
        foreach ($relations as $relation) {
93 21
            $combination = null;
94 21
            foreach (explode('.', $relation) as $part) {
95 21
                $combination .= is_null($combination) ? $part : ".{$part}";
96 21
                if (! in_array($combination, $combinations)) {
97 21
                    array_push($combinations, $combination);
98
                }
99
            }
100
        }
101
102 21
        return $combinations;
103
    }
104
105
    /**
106
     * Build and get query.
107
     *
108
     * @return EloquentBuilder|QueryBuilder
109
     */
110 51
    public function query()
111
    {
112 51
        if (! empty($this->allowedIncludes) && $this->request->filled(config('request-query-helper.include'))) {
113 21
            $this->loadIncludes($this->includes);
114
        }
115
116 48
        if ($this->request->filled(config('request-query-helper.filter'))) {
117 12
            $this->applyFilters($this->filters);
118
        }
119
120 45
        if ($this->request->filled(config('request-query-helper.sort'))) {
121 12
            $this->applySorts($this->sorts);
122
        }
123
124 42
        return $this->query;
125
    }
126
127
    /**
128
     * Execute the query.
129
     *
130
     * @return Collection
131
     */
132 27
    public function get()
133
    {
134 27
        return $this->query()->get();
135
    }
136
137
    /**
138
     * Execute the query and get the first result.
139
     *
140
     * @return Model|object|null
141
     */
142 21
    public function first()
143
    {
144 21
        return $this->query()
145 21
            ->take(1)
146 21
            ->get()
147 21
            ->first();
148
    }
149
150 21
    private function loadIncludes(QueryParamBag $includes)
151
    {
152
        $includes->each(function ($params, $relation) {
153 21
            if ($this->isAllowedToInclude($relation)) {
154 21
                $this->loadRelation($relation, $params);
155
            }
156 21
        });
157 18
    }
158
159 21
    private function isAllowedToInclude($relation): bool
160
    {
161 21
        return in_array($relation, $this->allowedIncludes);
162
    }
163
164 21
    private function loadRelation($relation, $params): void
165
    {
166 21
        if ($relationAlias = Arr::get($this->includesMap(), $relation)) {
167 3
            $this->query->with($relationAlias);
168
169 3
            return;
170
        }
171
172 18
        $methodName = 'include'.Str::studly(str_replace('.', 'With', $relation));
173 18
        if (method_exists($this, $methodName)) {
174 6
            $this->{$methodName}($this->query, $params);
175
176 6
            return;
177
        }
178
179 12
        if (is_null($this->allPossibleAvailableIncludes)) {
180 12
            $this->allPossibleAvailableIncludes = $this->getPossibleRelationshipCombinations($this->availableIncludes);
181
        }
182
183 12
        if (in_array($relation, $this->allPossibleAvailableIncludes)) {
184 9
            $this->query->with($relation);
185
186 9
            return;
187
        }
188
189 3
        throw new \RuntimeException("Trying to include non existing relationship {$relation}");
190
    }
191
192 12
    private function applyFilters(QueryParamBag $filters)
193
    {
194
        $filters->each(function ($params, $scope) {
195 12
            $this->applyFilter($scope, $params);
196 12
        });
197 9
    }
198
199 12
    private function applyFilter($scope, $params)
200
    {
201 12
        if ($filterAlias = Arr::get($this->filtersMap(), $scope)) {
202 3
            $this->query->{$filterAlias}($params);
203
204 3
            return;
205
        }
206
207 9
        $methodName = 'filterBy'.Str::studly($scope);
208
209 9
        if (method_exists($this, $methodName)) {
210 3
            $this->{$methodName}($this->query, $params);
211
212 3
            return;
213
        }
214
215 6
        if (in_array($scope, $this->availableFilters)) {
216 3
            $this->query->{$scope}($params);
217
218 3
            return;
219
        }
220
221 3
        throw new \RuntimeException("Trying to filter by non existing filter {$scope}");
222
    }
223
224 12
    private function applySorts(Sorts $sorts)
225
    {
226
        $sorts->each(function (SortField $sortField) {
227 12
            $this->applySort($sortField->getField(), $sortField->getDirection());
228 12
        });
229 9
    }
230
231 12
    private function applySort(string $field, string $direction)
232
    {
233 12
        if ($sortAlias = Arr::get($this->sortsMap(), $field)) {
234 3
            $this->query->orderBy($sortAlias, $direction);
235
236 3
            return;
237
        }
238
239 9
        $methodName = 'sortBy'.Str::studly($field);
240
241 9
        if (method_exists($this, $methodName)) {
242 3
            $this->{$methodName}($this->query, $direction);
243
244 3
            return;
245
        }
246
247 6
        if (in_array($field, $this->availableSorts)) {
248 3
            $this->query->orderBy($field, $direction);
249
250 3
            return;
251
        }
252
253 3
        throw new \RuntimeException("Trying to sort by non existing field {$field}");
254
    }
255
}
256