Completed
Push — master ( 1cac13...727973 )
by Freek
14s
created

QueryBuilder   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 5
dl 0
loc 185
rs 10
c 0
b 0
f 0

13 Methods

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