Completed
Push — master ( 540411...ff7aed )
by Freek
8s
created

QueryBuilder   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 4
dl 0
loc 171
rs 10
c 0
b 0
f 0

13 Methods

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