Completed
Pull Request — master (#35)
by
unknown
06:10
created

QueryBuilder::findFilter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 32 and the first side effect is on line 12.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

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

Loading history...
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
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
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 $columns;
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->columns = $this->request->column()) {
41
            $this->addSelectedColumns($this->columns);
42
        }
43
44
        if ($this->request->sorts()) {
45
            $this->allowedSorts('*');
46
        }
47
    }
48
49
    /**
50
     * Saves selects as a collection
51
     *
52
     * @param array|string $select
53
     * @return void
54
     */
55
    protected function addSelectedColumns($columns)
56
    {
57
        $columns = $columns->filter(function ($item) {
58
            return strpos($item, '.') === false;
59
        });
60
61
        if ($columns->count() > 0) {
62
            $this->select($columns->all());
63
        }
64
    }
65
66
    protected function getSelectForRelation($relation)
67
    {
68
        return $this->columns
69
            ->filter(function ($column) use ($relation) {
70
                return strpos($column, "{$relation}.") !== false;
71
            })
72
            ->map(function ($column) use ($relation) {
73
                return str_replace("{$relation}.", '', $column);
74
            });
75
76
    /**
77
     * Add the model, scopes, eager loaded relationships, local macro's and onDelete callback
78
     * from the $builder to this query builder.
79
     *
80
     * @param \Illuminate\Database\Eloquent\Builder $builder
81
     */
82
    protected function initializeFromBuilder(Builder $builder)
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_PROTECTED
Loading history...
83
    {
84
        $this->setModel($builder->getModel())
85
            ->setEagerLoads($builder->getEagerLoads());
86
87
        $builder->macro('getProtected', function (Builder $builder, string $property) {
88
            return $builder->{$property};
89
        });
90
91
        $this->scopes = $builder->getProtected('scopes');
92
93
        $this->localMacros = $builder->getProtected('localMacros');
94
95
        $this->onDelete = $builder->getProtected('onDelete');
96
    }
97
98
    /**
99
     * Create a new QueryBuilder for a request and model.
100
     *
101
     * @param string|\Illuminate\Database\Query\Builder $baseQuery Model class or base query builder
102
     * @param Request $request
103
     *
104
     * @return \Spatie\QueryBuilder\QueryBuilder
105
     */
106
    public static function for($baseQuery, ?Request $request = null): self
107
    {
108
        if (is_string($baseQuery)) {
109
            $baseQuery = ($baseQuery)::query();
110
        }
111
112
        return new static($baseQuery, $request ?? request());
113
    }
114
115
    public function allowedFilters($filters): self
116
    {
117
        $filters = is_array($filters) ? $filters : func_get_args();
118
        $this->allowedFilters = collect($filters)->map(function ($filter) {
119
            if ($filter instanceof Filter) {
120
                return $filter;
121
            }
122
123
            return Filter::partial($filter);
124
        });
125
126
        $this->guardAgainstUnknownFilters();
127
128
        $this->addFiltersToQuery($this->request->filters());
129
130
        return $this;
131
    }
132
133
    public function defaultSort($sort): self
134
    {
135
        $this->defaultSort = $sort;
136
137
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
138
139
        return $this;
140
    }
141
142
    public function allowedSorts($sorts): self
143
    {
144
        $sorts = is_array($sorts) ? $sorts : func_get_args();
145
        if (! $this->request->sorts()) {
146
            return $this;
147
        }
148
149
        $this->allowedSorts = collect($sorts);
150
151
        if (! $this->allowedSorts->contains('*')) {
152
            $this->guardAgainstUnknownSorts();
153
        }
154
155
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
156
157
        return $this;
158
    }
159
160
    public function allowedIncludes($includes): self
161
    {
162
        $includes = is_array($includes) ? $includes : func_get_args();
163
        $this->allowedIncludes = collect($includes);
164
165
        $this->guardAgainstUnknownIncludes();
166
167
        $this->addIncludesToQuery($this->request->includes());
168
169
        return $this;
170
    }
171
172
    protected function addFiltersToQuery(Collection $filters)
173
    {
174
        $filters->each(function ($value, $property) {
175
            $filter = $this->findFilter($property);
176
177
            $filter->filter($this, $value);
178
        });
179
    }
180
181
    protected function findFilter(string $property) : ?Filter
182
    {
183
        return $this->allowedFilters
184
            ->first(function (Filter $filter) use ($property) {
185
                return $filter->isForProperty($property);
186
            });
187
    }
188
189
    protected function addSortsToQuery(Collection $sorts)
190
    {
191
        $sorts
192
            ->each(function (string $sort) {
193
                $descending = $sort[0] === '-';
194
195
                $key = ltrim($sort, '-');
196
197
                $this->orderBy($key, $descending ? 'desc' : 'asc');
198
            });
199
    }
200
201
    protected function addIncludesToQuery(Collection $includes)
202
    {
203
        $includes
204
            ->map(function (string $include) {
205
                return camel_case($include);
206
            })
207
            ->each(function (string $include) {
208
                $selects = $this->getSelectForRelation(kebab_case($include));
209
210
                if (! $selects->count()) {
211
                    return $this->with($include);
212
                }
213
214
                $this->with([$include => function ($query) use ($selects) {
215
                    $query->select($selects->all());
216
                }]);
217
            });
218
    }
219
220
    protected function guardAgainstUnknownFilters()
221
    {
222
        $filterNames = $this->request->filters()->keys();
223
224
        $allowedFilterNames = $this->allowedFilters->map->getProperty();
225
226
        $diff = $filterNames->diff($allowedFilterNames);
227
228
        if ($diff->count()) {
229
            throw InvalidFilterQuery::filtersNotAllowed($diff, $allowedFilterNames);
230
        }
231
    }
232
233
    protected function guardAgainstUnknownSorts()
234
    {
235
        $sorts = $this->request->sorts()->map(function ($sort) {
236
            return ltrim($sort, '-');
237
        });
238
239
        $diff = $sorts->diff($this->allowedSorts);
240
241
        if ($diff->count()) {
242
            throw InvalidSortQuery::sortsNotAllowed($diff, $this->allowedSorts);
243
        }
244
    }
245
246
    protected function guardAgainstUnknownIncludes()
247
    {
248
        $includes = $this->request->includes();
249
250
        $diff = $includes->diff($this->allowedIncludes);
251
252
        if ($diff->count()) {
253
            throw InvalidIncludeQuery::includesNotAllowed($diff, $this->allowedIncludes);
254
        }
255
    }
256
}
257