Completed
Push — master ( 5c43fd...37ddbc )
by
unknown
01:38
created

SortsQuery::allowRepeatedParse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
Bug introduced by
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
0 ignored issues
show
Unused Code introduced by
The parameter $sorts is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
        $this->allowedSorts = $this->request->sorts()
128
            ->map(function ($sort) {
129
                $sortColumn = ltrim($sort, '-');
130
131
                // This is the only place where query string parameters are passed as
132
                // sort columns directly. We need to sanitize these column names.
133
                $sortColumn = ColumnNameSanitizer::sanitize($sortColumn);
134
135
                return Sort::field($sortColumn);
136
            });
137
138
        $this->generatedDefaultSorts = $this->request->sorts()->all();
139
    }
140
141
    protected function guardAgainstUnknownSorts()
142
    {
143
        $sortNames = $this->request->sorts()->map(function ($sort) {
144
            return ltrim($sort, '-');
145
        });
146
147
        $allowedSortNames = $this->allowedSorts->map->getProperty();
148
149
        $diff = $sortNames->diff($allowedSortNames);
150
151
        if ($diff->count()) {
152
            throw InvalidSortQuery::sortsNotAllowed($diff, $allowedSortNames);
153
        }
154
    }
155
156
    protected function allowRepeatedParse(): void
157
    {
158
        $this->sortsWereParsed = false;
159
    }
160
161
    protected function purgeGeneratedDefaultSorts(): void
162
    {
163
        $this->query->orders = collect($this->query->orders)
0 ignored issues
show
Bug introduced by
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...
164
            ->reject(function ($order) {
165
                return in_array($order['column'], $this->generatedDefaultSorts);
166
            })->values()->all();
167
    }
168
}
169