Completed
Pull Request — master (#35)
by
unknown
03:56
created

QueryBuilder::initializeFromBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 1
1
<?php
2
3
namespace Spatie\QueryBuilder;
4
5
use Illuminate\Http\Request;
6
use Illuminate\Support\Collection;
7
use Illuminate\Database\Eloquent\Builder;
8
use Spatie\QueryBuilder\Exceptions\InvalidSortQuery;
9
use Spatie\QueryBuilder\Exceptions\InvalidFilterQuery;
10
use Spatie\QueryBuilder\Exceptions\InvalidIncludeQuery;
11
12
class QueryBuilder extends Builder
13
{
14
    /** @var \Illuminate\Support\Collection */
15
    protected $allowedFilters;
16
17
    /** @var string|null */
18
    protected $defaultSort;
19
20
    /** @var \Illuminate\Support\Collection */
21
    protected $allowedSorts;
22
23
    /** @var \Illuminate\Support\Collection */
24
    protected $allowedIncludes;
25
26
    /** @var \Illuminate\Http\Request */
27
    protected $request;
28
29
    /** @var \Illuminate\Support\Collection */
30
    protected $fields;
31
32
    public function __construct(Builder $builder, ?Request $request = null)
33
    {
34
        parent::__construct(clone $builder->getQuery());
35
36
        $this->initializeFromBuilder($builder);
37
38
        $this->request = $request ?? request();
39
40
        if ($this->fields = $this->request->fields()) {
41
            $this->addSelectedFields($this->fields);
42
        }
43
44
        if ($this->request->sorts()) {
45
            $this->allowedSorts('*');
46
        }
47
    }
48
49
    /**
50
     * Adds fields to select statement
51
     *
52
     * @param \Illuminate\Support\Collection $fields
53
     * @return void
54
     */
55
    protected function addSelectedFields(Collection $fields)
56
    {
57
        $field = $fields->get(
58
            $this->getModel()->getTable()
59
        );
60
61
        if (is_null($field)) {
62
            return;
63
        }
64
65
        $this->select(explode(',', $field));
0 ignored issues
show
Bug introduced by
The method select() does not exist on Spatie\QueryBuilder\QueryBuilder. Did you maybe mean addSelectedFields()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
66
    }
67
68
    protected function getFieldsForRelation($relation)
69
    {
70
        $fields = $this->fields->get($relation);
71
72
        if (is_null($fields)) {
73
            return;
74
        }
75
76
        return explode(',', $fields);
77
    }
78
79
    /**
80
     * Add the model, scopes, eager loaded relationships, local macro's and onDelete callback
81
     * from the $builder to this query builder.
82
     *
83
     * @param \Illuminate\Database\Eloquent\Builder $builder
84
     */
85
    protected function initializeFromBuilder(Builder $builder)
86
    {
87
        $this->setModel($builder->getModel())
88
            ->setEagerLoads($builder->getEagerLoads());
89
90
        $builder->macro('getProtected', function (Builder $builder, string $property) {
91
            return $builder->{$property};
92
        });
93
94
        $this->scopes = $builder->getProtected('scopes');
95
96
        $this->localMacros = $builder->getProtected('localMacros');
97
98
        $this->onDelete = $builder->getProtected('onDelete');
99
    }
100
101
    /**
102
     * Create a new QueryBuilder for a request and model.
103
     *
104
     * @param string|\Illuminate\Database\Query\Builder $baseQuery Model class or base query builder
105
     * @param Request $request
106
     *
107
     * @return \Spatie\QueryBuilder\QueryBuilder
108
     */
109
    public static function for($baseQuery, ?Request $request = null): self
0 ignored issues
show
Coding Style introduced by
Possible parse error: non-abstract method defined as abstract
Loading history...
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
110
    {
111
        if (is_string($baseQuery)) {
112
            $baseQuery = ($baseQuery)::query();
113
        }
114
115
        return new static($baseQuery, $request ?? request());
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
Coding Style introduced by
The visibility should be declared for property $baseQuery.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
116
    }
117
118
    public function allowedFilters($filters): self
119
    {
120
        $filters = is_array($filters) ? $filters : func_get_args();
121
        $this->allowedFilters = collect($filters)->map(function ($filter) {
122
            if ($filter instanceof Filter) {
123
                return $filter;
124
            }
125
126
            return Filter::partial($filter);
127
        });
128
129
        $this->guardAgainstUnknownFilters();
130
131
        $this->addFiltersToQuery($this->request->filters());
132
133
        return $this;
134
    }
135
136
    public function defaultSort($sort): self
137
    {
138
        $this->defaultSort = $sort;
139
140
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
141
142
        return $this;
143
    }
144
145
    public function allowedSorts($sorts): self
146
    {
147
        $sorts = is_array($sorts) ? $sorts : func_get_args();
148
        if (! $this->request->sorts()) {
149
            return $this;
150
        }
151
152
        $this->allowedSorts = collect($sorts);
153
154
        if (! $this->allowedSorts->contains('*')) {
155
            $this->guardAgainstUnknownSorts();
156
        }
157
158
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
159
160
        return $this;
161
    }
162
163
    public function allowedIncludes($includes): self
164
    {
165
        $includes = is_array($includes) ? $includes : func_get_args();
166
        $this->allowedIncludes = collect($includes);
167
168
        $this->guardAgainstUnknownIncludes();
169
170
        $this->addIncludesToQuery($this->request->includes());
171
172
        return $this;
173
    }
174
175
    protected function addFiltersToQuery(Collection $filters)
176
    {
177
        $filters->each(function ($value, $property) {
178
            $filter = $this->findFilter($property);
179
180
            $filter->filter($this, $value);
181
        });
182
    }
183
184
    protected function findFilter(string $property) : ?Filter
185
    {
186
        return $this->allowedFilters
187
            ->first(function (Filter $filter) use ($property) {
188
                return $filter->isForProperty($property);
189
            });
190
    }
191
192
    protected function addSortsToQuery(Collection $sorts)
193
    {
194
        $sorts
195
            ->each(function (string $sort) {
196
                $descending = $sort[0] === '-';
197
198
                $key = ltrim($sort, '-');
199
200
                $this->orderBy($key, $descending ? 'desc' : 'asc');
0 ignored issues
show
Bug introduced by
The method orderBy() does not exist on Spatie\QueryBuilder\QueryBuilder. Did you maybe mean enforceOrderBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
201
            });
202
    }
203
204
    protected function addIncludesToQuery(Collection $includes)
205
    {
206
        $includes
207
            ->map(function (string $include) {
208
                return camel_case($include);
209
            })
210
            ->each(function (string $include) {
211
                $fields = $this->getFieldsForRelation(kebab_case($include));
212
213
                if (is_null($fields)) {
214
                    return $this->with($include);
215
                }
216
217
                $this->with([$include => function ($query) use ($fields) {
218
                    $query->select($fields);
219
                }]);
220
            });
221
    }
222
223
    protected function guardAgainstUnknownFilters()
224
    {
225
        $filterNames = $this->request->filters()->keys();
226
227
        $allowedFilterNames = $this->allowedFilters->map->getProperty();
228
229
        $diff = $filterNames->diff($allowedFilterNames);
230
231
        if ($diff->count()) {
232
            throw InvalidFilterQuery::filtersNotAllowed($diff, $allowedFilterNames);
233
        }
234
    }
235
236
    protected function guardAgainstUnknownSorts()
237
    {
238
        $sorts = $this->request->sorts()->map(function ($sort) {
239
            return ltrim($sort, '-');
240
        });
241
242
        $diff = $sorts->diff($this->allowedSorts);
243
244
        if ($diff->count()) {
245
            throw InvalidSortQuery::sortsNotAllowed($diff, $this->allowedSorts);
246
        }
247
    }
248
249
    protected function guardAgainstUnknownIncludes()
250
    {
251
        $includes = $this->request->includes();
252
253
        $diff = $includes->diff($this->allowedIncludes);
254
255
        if ($diff->count()) {
256
            throw InvalidIncludeQuery::includesNotAllowed($diff, $this->allowedIncludes);
257
        }
258
    }
259
}
260