Completed
Pull Request — master (#33)
by
unknown
01:50
created

QueryBuilder   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 26
lcom 2
cbo 4
dl 0
loc 204
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A initialiseFromBuilder() 0 16 1
A for() 0 8 2
A allowedFilters() 0 17 3
A defaultSort() 0 8 1
A allowedSorts() 0 17 4
A allowedIncludes() 0 11 2
A addFiltersToQuery() 0 8 1
A findFilter() 0 7 1
A addSortsToQuery() 0 11 2
A addIncludesToQuery() 0 10 1
A guardAgainstUnknownFilters() 0 12 2
A guardAgainstUnknownSorts() 0 12 2
A guardAgainstUnknownIncludes() 0 10 2
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\InvalidQuery;
9
10
class QueryBuilder extends Builder
11
{
12
    /** @var \Illuminate\Support\Collection */
13
    protected $allowedFilters;
14
15
    /** @var string|null */
16
    protected $defaultSort;
17
18
    /** @var \Illuminate\Support\Collection */
19
    protected $allowedSorts;
20
21
    /** @var \Illuminate\Support\Collection */
22
    protected $allowedIncludes;
23
24
    /** @var \Illuminate\Http\Request */
25
    protected $request;
26
27
    public function __construct(Builder $builder, ?Request $request = null)
28
    {
29
        parent::__construct(clone $builder->getQuery());
30
31
        $this->initialiseFromBuilder($builder);
32
33
        $this->request = $request ?? request();
34
35
        if ($this->request->sorts()) {
36
            $this->allowedSorts('*');
37
        }
38
    }
39
40
    /**
41
     * Add the model, scopes, eager loaded relationships, local macro's and onDelete callback
42
     * from the $builder to this query builder.
43
     *
44
     * @param \Illuminate\Database\Eloquent\Builder $builder
45
     */
46
    protected function initialiseFromBuilder(Builder $builder)
47
    {
48
        $this->setModel($builder->getModel());
49
50
        $this->setEagerLoads($builder->getEagerLoads());
51
52
        $builder->macro('getProtected', function ($builder, $property) {
53
            return $builder->{$property};
54
        });
55
56
        $this->scopes = $builder->getProtected('scopes');
57
58
        $this->localMacros = $builder->getProtected('localMacros');
59
60
        $this->onDelete = $builder->getProtected('onDelete');
61
    }
62
63
    /**
64
     * Create a new QueryBuilder for a request and model.
65
     *
66
     * @param string|\Illuminate\Database\Query\Builder $baseQuery Model class or base query builder
67
     * @param Request $request
68
     *
69
     * @return \Spatie\QueryBuilder\QueryBuilder
70
     */
71
    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...
72
    {
73
        if (is_string($baseQuery)) {
74
            $baseQuery = ($baseQuery)::query();
75
        }
76
77
        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...
78
    }
79
80
    public function allowedFilters($filters): self
81
    {
82
        $filters = is_array($filters) ? $filters : func_get_args();
83
        $this->allowedFilters = collect($filters)->map(function ($filter) {
84
            if ($filter instanceof Filter) {
85
                return $filter;
86
            }
87
88
            return Filter::partial($filter);
89
        });
90
91
        $this->guardAgainstUnknownFilters();
92
93
        $this->addFiltersToQuery($this->request->filters());
94
95
        return $this;
96
    }
97
98
    public function defaultSort($sort): self
99
    {
100
        $this->defaultSort = $sort;
101
102
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
103
104
        return $this;
105
    }
106
107
    public function allowedSorts($sorts): self
108
    {
109
        $sorts = is_array($sorts) ? $sorts : func_get_args();
110
        if (! $this->request->sorts()) {
111
            return $this;
112
        }
113
114
        $this->allowedSorts = collect($sorts);
115
116
        if (! $this->allowedSorts->contains('*')) {
117
            $this->guardAgainstUnknownSorts();
118
        }
119
120
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
121
122
        return $this;
123
    }
124
125
    public function allowedIncludes($includes): self
126
    {
127
        $includes = is_array($includes) ? $includes : func_get_args();
128
        $this->allowedIncludes = collect($includes);
129
130
        $this->guardAgainstUnknownIncludes();
131
132
        $this->addIncludesToQuery($this->request->includes());
133
134
        return $this;
135
    }
136
137
    protected function addFiltersToQuery(Collection $filters)
138
    {
139
        $filters->each(function ($value, $property) {
140
            $filter = $this->findFilter($property);
141
142
            $filter->filter($this, $value);
143
        });
144
    }
145
146
    protected function findFilter(string $property) : ?Filter
147
    {
148
        return $this->allowedFilters
149
            ->first(function (Filter $filter) use ($property) {
150
                return $filter->isForProperty($property);
151
            });
152
    }
153
154
    protected function addSortsToQuery(Collection $sorts)
155
    {
156
        $sorts
157
            ->each(function (string $sort) {
158
                $descending = $sort[0] === '-';
159
160
                $key = ltrim($sort, '-');
161
162
                $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...
163
            });
164
    }
165
166
    protected function addIncludesToQuery(Collection $includes)
167
    {
168
        $includes
169
            ->map(function (string $include) {
170
                return camel_case($include);
171
            })
172
            ->each(function (string $include) {
173
                $this->with($include);
174
            });
175
    }
176
177
    protected function guardAgainstUnknownFilters()
178
    {
179
        $filterNames = $this->request->filters()->keys();
180
181
        $allowedFilterNames = $this->allowedFilters->map->getProperty();
182
183
        $diff = $filterNames->diff($allowedFilterNames);
184
185
        if ($diff->count()) {
186
            throw InvalidQuery::filtersNotAllowed($diff, $allowedFilterNames);
187
        }
188
    }
189
190
    protected function guardAgainstUnknownSorts()
191
    {
192
        $sorts = $this->request->sorts()->map(function ($sort) {
193
            return ltrim($sort, '-');
194
        });
195
196
        $diff = $sorts->diff($this->allowedSorts);
197
198
        if ($diff->count()) {
199
            throw InvalidQuery::sortsNotAllowed($diff, $this->allowedSorts);
200
        }
201
    }
202
203
    protected function guardAgainstUnknownIncludes()
204
    {
205
        $includes = $this->request->includes();
206
207
        $diff = $includes->diff($this->allowedIncludes);
208
209
        if ($diff->count()) {
210
            throw InvalidQuery::includesNotAllowed($diff, $this->allowedIncludes);
211
        }
212
    }
213
}
214