Passed
Push — master ( 7b91f0...dc6f50 )
by Milroy
02:11
created

QueryBuilderAbstract::parseAllowedIncludes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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