This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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 Zbiller\Sort\Traits; |
||
4 | |||
5 | use Zbiller\Sort\Objects\Sort; |
||
6 | use Illuminate\Database\Eloquent\Model; |
||
7 | use Illuminate\Database\Eloquent\Builder; |
||
8 | use Zbiller\Sort\Exceptions\SortException; |
||
9 | use Illuminate\Database\Eloquent\Relations\HasOne; |
||
10 | use Illuminate\Database\Eloquent\Relations\BelongsTo; |
||
11 | |||
12 | trait IsSortable |
||
13 | { |
||
14 | /** |
||
15 | * @var array |
||
16 | */ |
||
17 | protected $sort = [ |
||
18 | /* |
||
19 | * The query builder instance from the Sorted scope. |
||
20 | * |
||
21 | * @var Builder |
||
22 | */ |
||
23 | 'query' => null, |
||
24 | |||
25 | /* |
||
26 | * The data applying the "sorted" scope on a model. |
||
27 | * |
||
28 | * @var array |
||
29 | */ |
||
30 | 'data' => null, |
||
31 | |||
32 | /* |
||
33 | * The Zbiller\Sort\Objects\Sort instance. |
||
34 | * This is used to get the sorting rules, just like a request. |
||
35 | * |
||
36 | * @var Sort |
||
37 | */ |
||
38 | 'instance' => null, |
||
39 | |||
40 | /* |
||
41 | * The field to sort by. |
||
42 | * |
||
43 | * @var string |
||
44 | */ |
||
45 | 'field' => Sort::DEFAULT_SORT_FIELD, |
||
46 | |||
47 | /* |
||
48 | * The direction to sort in. |
||
49 | * |
||
50 | * @var string |
||
51 | */ |
||
52 | 'direction' => Sort::DEFAULT_DIRECTION_FIELD, |
||
53 | ]; |
||
54 | |||
55 | /** |
||
56 | * The filter scope. |
||
57 | * Should be called on the model when building the query. |
||
58 | * |
||
59 | * @param Builder $query |
||
60 | * @param array $data |
||
61 | * @param Sort $sort |
||
62 | */ |
||
63 | public function scopeSorted($query, array $data, Sort $sort = null) |
||
64 | { |
||
65 | $this->sort['query'] = $query; |
||
66 | $this->sort['data'] = $data; |
||
67 | $this->sort['instance'] = $sort; |
||
68 | |||
69 | $this->setFieldToSortBy(); |
||
70 | $this->setDirectionToSortIn(); |
||
71 | |||
72 | if ($this->isValidSort()) { |
||
73 | $this->checkSortingDirection(); |
||
74 | |||
75 | switch ($this->sort['data'][$this->sort['direction']]) { |
||
76 | case Sort::DIRECTION_RANDOM: |
||
77 | $this->sort['query']->inRandomOrder(); |
||
78 | break; |
||
79 | default: |
||
80 | if ($this->shouldSortByRelation()) { |
||
81 | $this->sortByRelation(); |
||
82 | } else { |
||
83 | $this->sortNormally(); |
||
84 | } |
||
85 | } |
||
86 | } |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Verify if all sorting conditions are met. |
||
91 | * |
||
92 | * @return bool |
||
93 | */ |
||
94 | protected function isValidSort() |
||
95 | { |
||
96 | return |
||
97 | isset($this->sort['data'][$this->sort['field']]) && |
||
98 | isset($this->sort['data'][$this->sort['direction']]); |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * Set the sort field if an Zbiller\Sort\Objects\Sort instance has been provided as a parameter for the sorted scope. |
||
103 | * |
||
104 | * @return void |
||
105 | */ |
||
106 | protected function setFieldToSortBy() |
||
107 | { |
||
108 | if ($this->sort['instance'] instanceof Sort) { |
||
109 | $this->sort['field'] = $this->sort['instance']->field(); |
||
110 | } |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * Set the sort direction if an Zbiller\Sort\Objects\Sort instance has been provided as a parameter for the sorted scope. |
||
115 | * |
||
116 | * @return void |
||
117 | */ |
||
118 | protected function setDirectionToSortIn() |
||
119 | { |
||
120 | if ($this->sort['instance'] instanceof Sort) { |
||
121 | $this->sort['direction'] = $this->sort['instance']->direction(); |
||
122 | } |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Sort model records using columns from the model's table itself. |
||
127 | * |
||
128 | * @return void |
||
129 | */ |
||
130 | protected function sortNormally() |
||
131 | { |
||
132 | $this->sort['query']->orderBy( |
||
133 | $this->sort['data'][$this->sort['field']], |
||
134 | $this->sort['data'][$this->sort['direction']] |
||
135 | ); |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Sort model records using columns from the model relation's table. |
||
140 | * |
||
141 | * @return void |
||
142 | */ |
||
143 | protected function sortByRelation() |
||
144 | { |
||
145 | $parts = explode('.', $this->sort['data'][$this->sort['field']]); |
||
146 | $models = []; |
||
147 | |||
148 | if (count($parts) > 2) { |
||
149 | $field = array_pop($parts); |
||
150 | $relations = $parts; |
||
151 | } else { |
||
152 | $field = array_last($parts); |
||
153 | $relations = (array) array_first($parts); |
||
154 | } |
||
155 | |||
156 | foreach ($relations as $index => $relation) { |
||
157 | $previousModel = $this; |
||
158 | |||
159 | if (isset($models[$index - 1])) { |
||
160 | $previousModel = $models[$index - 1]; |
||
161 | } |
||
162 | |||
163 | $this->checkRelationToSortBy($previousModel, $relation); |
||
164 | |||
165 | $models[] = $previousModel->{$relation}()->getModel(); |
||
166 | |||
167 | $modelTable = $previousModel->getTable(); |
||
168 | $relationTable = $previousModel->{$relation}()->getModel()->getTable(); |
||
169 | $foreignKey = $previousModel->{$relation}() instanceof HasOne ? |
||
170 | $previousModel->{$relation}()->getForeignKeyName() : |
||
171 | $previousModel->{$relation}()->getForeignKey(); |
||
172 | |||
173 | if (! $this->alreadyJoinedForSorting($relationTable)) { |
||
174 | switch (get_class($previousModel->{$relation}())) { |
||
175 | View Code Duplication | case BelongsTo::class: |
|
0 ignored issues
–
show
|
|||
176 | $this->sort['query']->join($relationTable, $modelTable.'.'.$foreignKey, '=', $relationTable.'.id'); |
||
177 | break; |
||
178 | View Code Duplication | case HasOne::class: |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
179 | $this->sort['query']->join($relationTable, $modelTable.'.id', '=', $relationTable.'.'.$foreignKey); |
||
180 | break; |
||
181 | } |
||
182 | } |
||
183 | } |
||
184 | |||
185 | $alias = implode('_', $relations).'_'.$field; |
||
186 | |||
187 | if (isset($relationTable)) { |
||
188 | $this->sort['query']->addSelect([ |
||
189 | $this->getTable().'.*', |
||
0 ignored issues
–
show
It seems like
getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?
This check looks for methods that are used by a trait but not required by it. To illustrate, let’s look at the following code example trait Idable {
public function equalIds(Idable $other) {
return $this->getId() === $other->getId();
}
}
The trait Adding the
Loading history...
|
|||
190 | $relationTable.'.'.$field.' AS '.$alias, |
||
191 | ]); |
||
192 | } |
||
193 | |||
194 | $this->sort['query']->orderBy( |
||
195 | $alias, $this->sort['data'][$this->sort['direction']] |
||
196 | ); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * @return bool |
||
201 | */ |
||
202 | protected function shouldSortByRelation() |
||
203 | { |
||
204 | return str_contains($this->sort['data'][$this->sort['field']], '.'); |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Verify if the desired join exists already, possibly included by a global scope. |
||
209 | * |
||
210 | * @param string $table |
||
211 | * |
||
212 | * @return bool |
||
213 | */ |
||
214 | protected function alreadyJoinedForSorting($table) |
||
215 | { |
||
216 | return str_contains(strtolower($this->sort['query']->toSql()), 'join `'.$table.'`'); |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Verify if the direction provided matches one of the directions from: |
||
221 | * Zbiller\Sort\Objects\Sort::$directions. |
||
222 | * |
||
223 | * @return void |
||
224 | */ |
||
225 | protected function checkSortingDirection() |
||
226 | { |
||
227 | if (! in_array(strtolower($this->sort['data'][$this->sort['direction']]), array_map('strtolower', Sort::$directions))) { |
||
228 | throw SortException::invalidDirectionSupplied($this->sort['data'][$this->sort['direction']]); |
||
229 | } |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Verify if the desired relation to sort by is one of: HasOne or BelongsTo. |
||
234 | * Sorting by "many" relations or "morph" ones is not possible. |
||
235 | * |
||
236 | * @param Model $model |
||
237 | * @param string $relation |
||
238 | */ |
||
239 | protected function checkRelationToSortBy(Model $model, $relation) |
||
240 | { |
||
241 | if (! ($model->{$relation}() instanceof HasOne) && ! ($model->{$relation}() instanceof BelongsTo)) { |
||
242 | throw SortException::wrongRelationToSort($relation, get_class($model->{$relation}())); |
||
243 | } |
||
244 | } |
||
245 | } |
||
246 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.