Completed
Push — master ( 77de06...45cc8d )
by
unknown
01:29
created

src/Concerns/SortsQuery.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Spatie\QueryBuilder\Concerns;
4
5
use Spatie\QueryBuilder\Sort;
6
use Illuminate\Support\Collection;
7
use Spatie\QueryBuilder\ColumnNameSanitizer;
8
use Spatie\QueryBuilder\Exceptions\InvalidSortQuery;
9
10
trait SortsQuery
11
{
12
    /** @var \Illuminate\Support\Collection */
13
    protected $defaultSorts;
14
15
    /** @var \Illuminate\Support\Collection */
16
    protected $allowedSorts;
17
18
    /** @var bool */
19
    protected $sortsWereParsed = false;
20
21
    /**
22
     * Per default, sorting is allowed on all columns if not specified otherwise.
23
     * We keep track of those default sorts to purge them if, at a later point in time, allowed sorts are specified.
24
     *
25
     * @var array
26
     */
27
    protected $generatedDefaultSorts = [];
28
29
    public function allowedSorts($sorts): self
30
    {
31
        $sorts = is_array($sorts) ? $sorts : func_get_args();
32
33
        if (! $this->request->sorts()) {
0 ignored issues
show
The property request does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
34
            return $this;
35
        }
36
37
        $this->allowedSorts = collect($sorts)->map(function ($sort) {
38
            if ($sort instanceof Sort) {
39
                return $sort;
40
            }
41
42
            return Sort::field(ltrim($sort, '-'));
43
        });
44
45
        $this->guardAgainstUnknownSorts();
46
47
        return $this;
48
    }
49
50
    /**
51
     * @param array|string|\Spatie\QueryBuilder\Sort $sorts
52
     *
53
     * @return \Spatie\QueryBuilder\QueryBuilder
54
     */
55
    public function defaultSort($sorts): self
56
    {
57
        return $this->defaultSorts(func_get_args());
58
    }
59
60
    /**
61
     * @param array|string|\Spatie\QueryBuilder\Sort $sorts
62
     *
63
     * @return \Spatie\QueryBuilder\QueryBuilder
64
     */
65
    public function defaultSorts($sorts): self
66
    {
67
        $sorts = is_array($sorts) ? $sorts : func_get_args();
68
69
        $this->defaultSorts = collect($sorts)->map(function ($sort) {
70
            if (is_string($sort)) {
71
                return Sort::field($sort);
72
            }
73
74
            return $sort;
75
        });
76
77
        return $this;
78
    }
79
80
    protected function parseSorts()
81
    {
82
        // Avoid repeated calls when used by e.g. 'paginate'
83
        if ($this->sortsWereParsed) {
84
            return;
85
        }
86
87
        $this->sortsWereParsed = true;
88
89
        if (! $this->allowedSorts instanceof Collection) {
90
            $this->addDefaultSorts();
91
            $this->allowRepeatedParse();
92
        } else {
93
            $this->purgeGeneratedDefaultSorts();
94
        }
95
96
        $sorts = $this->request->sorts();
97
98
        if ($sorts->isEmpty()) {
99
            optional($this->defaultSorts)->each(function (Sort $sort) {
100
                $sort->sort($this);
101
            });
102
        }
103
104
        $sorts
105
            ->each(function (string $property) {
106
                $descending = $property[0] === '-';
107
108
                $key = ltrim($property, '-');
109
110
                $sort = $this->findSort($key);
111
112
                $sort->sort($this, $descending);
113
            });
114
    }
115
116
    protected function findSort(string $property): ?Sort
117
    {
118
        return $this->allowedSorts
119
            ->merge($this->defaultSorts)
120
            ->first(function (Sort $sort) use ($property) {
121
                return $sort->isForProperty($property);
122
            });
123
    }
124
125
    protected function addDefaultSorts()
126
    {
127
        $sanitizedSortColumns = $this->request->sorts()->map(function ($sort) {
128
            $sortColumn = ltrim($sort, '-');
129
130
            // This is the only place where query string parameters are passed as
131
            // sort columns directly. We need to sanitize these column names.
132
            return ColumnNameSanitizer::sanitize($sortColumn);
133
        });
134
135
        $this->allowedSorts = $sanitizedSortColumns->map(function ($column) {
136
            return Sort::field($column);
137
        });
138
139
        $this->generatedDefaultSorts = $sanitizedSortColumns->toArray();
140
    }
141
142
    protected function guardAgainstUnknownSorts()
143
    {
144
        $sortNames = $this->request->sorts()->map(function ($sort) {
145
            return ltrim($sort, '-');
146
        });
147
148
        $allowedSortNames = $this->allowedSorts->map->getProperty();
149
150
        $diff = $sortNames->diff($allowedSortNames);
151
152
        if ($diff->count()) {
153
            throw InvalidSortQuery::sortsNotAllowed($diff, $allowedSortNames);
154
        }
155
    }
156
157
    protected function allowRepeatedParse(): void
158
    {
159
        $this->sortsWereParsed = false;
160
    }
161
162
    protected function purgeGeneratedDefaultSorts(): void
163
    {
164
        $this->query->orders = collect($this->query->orders)
0 ignored issues
show
The property query does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
165
            ->reject(function ($order) {
166
                if (! isset($order['column'])) {
167
                    return false;
168
                }
169
170
                return in_array($order['column'], $this->generatedDefaultSorts);
171
            })->values()->all();
172
    }
173
}
174