Completed
Pull Request — master (#24)
by
unknown
01:23
created

QueryBuilder::addSortsToQuery()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
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\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
        $this->allowedFilters = collect($filters)->map(function ($filter) {
65
            if ($filter instanceof Filter) {
66
                return $filter;
67
            }
68
69
            return Filter::partial($filter);
70
        });
71
72
        $this->guardAgainstUnknownFilters();
73
74
        $this->addFiltersToQuery($this->request->filters());
75
76
        return $this;
77
    }
78
79
    public function defaultSort($sort): self
80
    {
81
        $this->defaultSort = $sort;
82
83
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
84
85
        return $this;
86
    }
87
88
    public function allowedSorts(...$sorts): self
89
    {
90
        if (! $this->request->sorts()) {
91
            return $this;
92
        }
93
94
        $this->allowedSorts = collect($sorts);
95
96
        if (! $this->allowedSorts->contains('*')) {
97
            $this->guardAgainstUnknownSorts();
98
        }
99
100
        $this->addSortsToQuery($this->request->sorts($this->defaultSort));
101
102
        return $this;
103
    }
104
105
    public function allowedIncludes(...$includes): self
106
    {
107
        $this->allowedIncludes = collect($includes);
108
109
        $this->guardAgainstUnknownIncludes();
110
111
        $this->addIncludesToQuery($this->request->includes());
112
113
        return $this;
114
    }
115
116
    protected function addFiltersToQuery(Collection $filters)
117
    {
118
        $filters->each(function ($value, $property) {
119
            $filter = $this->findFilter($property);
120
121
            $filter->filter($this, $value);
122
        });
123
    }
124
125
    protected function findFilter(string $property) : ?Filter
126
    {
127
        return $this->allowedFilters
128
            ->first(function (Filter $filter) use ($property) {
129
                return $filter->isForProperty($property);
130
            });
131
    }
132
133
    protected function addSortsToQuery(Collection $sorts)
134
    {
135
        $sorts
136
            ->each(function (string $sort) {
137
                $descending = $sort[0] === '-';
138
139
                $key = ltrim($sort, '-');
140
141
                $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...
142
            });
143
    }
144
145
    protected function addIncludesToQuery(Collection $includes)
146
    {
147
        $includes
148
            ->map(function (string $include) {
149
                return camel_case($include);
150
            })
151
            ->each(function (string $include) {
152
                $this->with($include);
153
            });
154
    }
155
156
    protected function guardAgainstUnknownFilters()
157
    {
158
        $filterNames = $this->request->filters()->keys();
159
160
        $allowedFilterNames = $this->allowedFilters->map->getProperty();
161
162
        $diff = $filterNames->diff($allowedFilterNames);
163
164
        if ($diff->count()) {
165
            throw InvalidQuery::filtersNotAllowed($diff, $allowedFilterNames);
166
        }
167
    }
168
169
    protected function guardAgainstUnknownSorts()
170
    {
171
        $sorts = $this->request->sorts()->map(function ($sort) {
172
            return ltrim($sort, '-');
173
        });
174
175
        $diff = $sorts->diff($this->allowedSorts);
176
177
        if ($diff->count()) {
178
            throw InvalidQuery::sortsNotAllowed($diff, $this->allowedSorts);
179
        }
180
    }
181
182
    protected function guardAgainstUnknownIncludes()
183
    {
184
        $includes = $this->request->includes();
185
186
        $diff = $includes->diff($this->allowedIncludes);
187
188
        if ($diff->count()) {
189
            throw InvalidQuery::includesNotAllowed($diff, $this->allowedIncludes);
190
        }
191
    }
192
}
193