|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Charcoal\Source; |
|
4
|
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
|
6
|
|
|
|
|
7
|
|
|
// From 'charcoal-core' |
|
8
|
|
|
use Charcoal\Source\Expression; |
|
9
|
|
|
use Charcoal\Source\ExpressionFieldTrait; |
|
10
|
|
|
use Charcoal\Source\OrderInterface; |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* Order Expression |
|
14
|
|
|
* |
|
15
|
|
|
* For sorting the results of a query. |
|
16
|
|
|
* |
|
17
|
|
|
* Possible sorting modes: |
|
18
|
|
|
* 1. Custom — Sort results by a custom expression. |
|
19
|
|
|
* - Requirement: "condition" |
|
20
|
|
|
* 2. Values — Sort results against a defined order. |
|
21
|
|
|
* - Requirements: "values", "property" |
|
22
|
|
|
* - Complementary: "direction" (useful for sorting unlisted results) |
|
23
|
|
|
* 3. Direction — Sort results in ascending or descending order. |
|
24
|
|
|
* - Requirements: "property" |
|
25
|
|
|
* - Complementary: "direction" (ascending, by default) |
|
26
|
|
|
*/ |
|
27
|
|
|
class Order extends Expression implements |
|
28
|
|
|
OrderInterface |
|
29
|
|
|
{ |
|
30
|
|
|
use ExpressionFieldTrait; |
|
31
|
|
|
|
|
32
|
|
|
const MODE_ASC = 'asc'; |
|
33
|
|
|
const MODE_DESC = 'desc'; |
|
34
|
|
|
const MODE_RANDOM = 'rand'; |
|
35
|
|
|
const MODE_VALUES = 'values'; |
|
36
|
|
|
const MODE_CUSTOM = 'custom'; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* The sort mode. |
|
40
|
|
|
* |
|
41
|
|
|
* @var string|null |
|
42
|
|
|
*/ |
|
43
|
|
|
protected $mode; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* The values to sort against when {@see self::$mode} is {@see self::MODE_VALUES}. |
|
47
|
|
|
* |
|
48
|
|
|
* @var array|null |
|
49
|
|
|
*/ |
|
50
|
|
|
protected $values; |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* The direction of sorting. |
|
54
|
|
|
* |
|
55
|
|
|
* @var string|null |
|
56
|
|
|
*/ |
|
57
|
|
|
protected $direction; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* Set the order clause data. |
|
61
|
|
|
* |
|
62
|
|
|
* @param array<string,mixed> $data The expression data; |
|
63
|
|
|
* as an associative array. |
|
64
|
|
|
* @return self |
|
65
|
|
|
*/ |
|
66
|
|
|
public function setData(array $data) |
|
67
|
|
|
{ |
|
68
|
|
|
parent::setData($data); |
|
69
|
|
|
|
|
70
|
|
|
/** @deprecated */ |
|
71
|
|
View Code Duplication |
if (isset($data['string'])) { |
|
|
|
|
|
|
72
|
|
|
trigger_error( |
|
73
|
|
|
sprintf( |
|
74
|
|
|
'Sort expression option "string" is deprecated in favour of "condition": %s', |
|
75
|
|
|
$data['string'] |
|
76
|
|
|
), |
|
77
|
|
|
E_USER_DEPRECATED |
|
78
|
|
|
); |
|
79
|
|
|
$this->setCondition($data['string']); |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** @deprecated */ |
|
83
|
|
View Code Duplication |
if (isset($data['table_name'])) { |
|
|
|
|
|
|
84
|
|
|
trigger_error( |
|
85
|
|
|
sprintf( |
|
86
|
|
|
'Sort expression option "table_name" is deprecated in favour of "table": %s', |
|
87
|
|
|
$data['table_name'] |
|
88
|
|
|
), |
|
89
|
|
|
E_USER_DEPRECATED |
|
90
|
|
|
); |
|
91
|
|
|
$this->setTable($data['table_name']); |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
if (isset($data['table'])) { |
|
95
|
|
|
$this->setTable($data['table']); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
if (isset($data['property'])) { |
|
99
|
|
|
$this->setProperty($data['property']); |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
if (isset($data['direction'])) { |
|
103
|
|
|
$this->setDirection($data['direction']); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
if (isset($data['mode'])) { |
|
107
|
|
|
$this->setMode($data['mode']); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
if (isset($data['values'])) { |
|
111
|
|
|
$this->setValues($data['values']); |
|
112
|
|
|
|
|
113
|
|
|
if (!isset($data['mode'])) { |
|
114
|
|
|
$this->setMode(self::MODE_VALUES); |
|
115
|
|
|
} |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
if (isset($data['condition']) || isset($data['string'])) { |
|
119
|
|
|
if (!isset($data['mode'])) { |
|
120
|
|
|
$this->setMode(self::MODE_CUSTOM); |
|
121
|
|
|
} |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
return $this; |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
/** |
|
128
|
|
|
* Retrieve the default values for sorting. |
|
129
|
|
|
* |
|
130
|
|
|
* @return array<string,mixed> An associative array. |
|
131
|
|
|
*/ |
|
132
|
|
|
public function defaultData() |
|
133
|
|
|
{ |
|
134
|
|
|
return [ |
|
135
|
|
|
'property' => null, |
|
136
|
|
|
'table' => null, |
|
137
|
|
|
'direction' => null, |
|
138
|
|
|
'mode' => null, |
|
139
|
|
|
'values' => null, |
|
140
|
|
|
'condition' => null, |
|
141
|
|
|
'active' => true, |
|
142
|
|
|
'name' => null, |
|
143
|
|
|
]; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
/** |
|
147
|
|
|
* Retrieve the order clause structure. |
|
148
|
|
|
* |
|
149
|
|
|
* @return array<string,mixed> An associative array. |
|
150
|
|
|
*/ |
|
151
|
|
|
public function data() |
|
152
|
|
|
{ |
|
153
|
|
|
return [ |
|
154
|
|
|
'property' => $this->property(), |
|
155
|
|
|
'table' => $this->table(), |
|
156
|
|
|
'direction' => $this->direction(), |
|
157
|
|
|
'mode' => $this->mode(), |
|
158
|
|
|
'values' => $this->values(), |
|
159
|
|
|
'condition' => $this->condition(), |
|
160
|
|
|
'active' => $this->active(), |
|
161
|
|
|
'name' => $this->name(), |
|
162
|
|
|
]; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* Set the, pre-defined, sorting mode. |
|
167
|
|
|
* |
|
168
|
|
|
* @param string|null $mode The sorting mode. |
|
169
|
|
|
* @throws InvalidArgumentException If the mode is not a string or invalid. |
|
170
|
|
|
* @return self |
|
171
|
|
|
*/ |
|
172
|
|
|
public function setMode($mode) |
|
173
|
|
|
{ |
|
174
|
|
|
if ($mode === null) { |
|
175
|
|
|
$this->mode = $mode; |
|
176
|
|
|
return $this; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
if (!is_string($mode)) { |
|
180
|
|
|
throw new InvalidArgumentException( |
|
181
|
|
|
'Order Mode must be a string.' |
|
182
|
|
|
); |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
$mode = strtolower($mode); |
|
186
|
|
|
$valid = $this->validModes(); |
|
187
|
|
|
if (!in_array($mode, $valid)) { |
|
188
|
|
|
throw new InvalidArgumentException(sprintf( |
|
189
|
|
|
'Invalid Order Mode. Must be one of "%s"', |
|
190
|
|
|
implode('", "', $valid) |
|
191
|
|
|
)); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
if (in_array($mode, [ self::MODE_ASC, self::MODE_DESC ])) { |
|
195
|
|
|
$this->setDirection($mode); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
$this->mode = $mode; |
|
199
|
|
|
return $this; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* Retrieve the sorting mode. |
|
204
|
|
|
* |
|
205
|
|
|
* @return string|null |
|
206
|
|
|
*/ |
|
207
|
|
|
public function mode() |
|
208
|
|
|
{ |
|
209
|
|
|
return $this->mode; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* Set the sorting direction. |
|
214
|
|
|
* |
|
215
|
|
|
* @param string|null $direction The direction to sort on. |
|
216
|
|
|
* @throws InvalidArgumentException If the direction is not a string. |
|
217
|
|
|
* @return self |
|
218
|
|
|
*/ |
|
219
|
|
View Code Duplication |
public function setDirection($direction) |
|
220
|
|
|
{ |
|
221
|
|
|
if ($direction === null) { |
|
222
|
|
|
$this->direction = $direction; |
|
223
|
|
|
return $this; |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
if (!is_string($direction)) { |
|
227
|
|
|
throw new InvalidArgumentException( |
|
228
|
|
|
'Direction must be a string.' |
|
229
|
|
|
); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
$this->direction = strtolower($direction) === 'asc' ? 'ASC' : 'DESC'; |
|
233
|
|
|
return $this; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* Retrieve the sorting direction. |
|
238
|
|
|
* |
|
239
|
|
|
* @return string|null |
|
240
|
|
|
*/ |
|
241
|
|
|
public function direction() |
|
242
|
|
|
{ |
|
243
|
|
|
return $this->direction; |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
/** |
|
247
|
|
|
* Set the values to sort against. |
|
248
|
|
|
* |
|
249
|
|
|
* Note: Values are ignored if the mode is not {@see self::MODE_VALUES}. |
|
250
|
|
|
* |
|
251
|
|
|
* @throws InvalidArgumentException If the parameter is not an array or a string. |
|
252
|
|
|
* @param string|array|null $values A list of field values. |
|
253
|
|
|
* If the $values parameter: |
|
254
|
|
|
* - is a string, the string will be split by ",". |
|
255
|
|
|
* - is an array, the values will be used as is. |
|
256
|
|
|
* - any other data type throws an exception. |
|
257
|
|
|
* @return self |
|
258
|
|
|
*/ |
|
259
|
|
|
public function setValues($values) |
|
260
|
|
|
{ |
|
261
|
|
|
if ($values === null) { |
|
262
|
|
|
$this->values = $values; |
|
263
|
|
|
return $this; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
if (is_string($values)) { |
|
267
|
|
|
if ($values === '') { |
|
268
|
|
|
throw new InvalidArgumentException( |
|
269
|
|
|
'String values can not be empty.' |
|
270
|
|
|
); |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
$values = array_map('trim', explode(',', $values)); |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
if (is_array($values)) { |
|
277
|
|
|
if (empty($values)) { |
|
278
|
|
|
throw new InvalidArgumentException( |
|
279
|
|
|
'Array values can not be empty.' |
|
280
|
|
|
); |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
$this->values = $values; |
|
284
|
|
|
return $this; |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
throw new InvalidArgumentException(sprintf( |
|
288
|
|
|
'Order Values must be an array or comma-delimited string, received %s', |
|
289
|
|
|
is_object($values) ? get_class($values) : gettype($values) |
|
290
|
|
|
)); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Determine if the Order expression has values. |
|
295
|
|
|
* |
|
296
|
|
|
* @return boolean |
|
297
|
|
|
*/ |
|
298
|
|
|
public function hasValues() |
|
299
|
|
|
{ |
|
300
|
|
|
return !empty($this->values); |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
|
|
/** |
|
304
|
|
|
* Retrieve the values to sort against. |
|
305
|
|
|
* |
|
306
|
|
|
* @return array|null A list of field values or NULL if no values were assigned. |
|
307
|
|
|
*/ |
|
308
|
|
|
public function values() |
|
309
|
|
|
{ |
|
310
|
|
|
return $this->values; |
|
311
|
|
|
} |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* Retrieve the supported sorting modes. |
|
315
|
|
|
* |
|
316
|
|
|
* @return array |
|
317
|
|
|
*/ |
|
318
|
|
|
protected function validModes() |
|
319
|
|
|
{ |
|
320
|
|
|
return [ |
|
321
|
|
|
self::MODE_DESC, |
|
322
|
|
|
self::MODE_ASC, |
|
323
|
|
|
self::MODE_RANDOM, |
|
324
|
|
|
self::MODE_VALUES, |
|
325
|
|
|
self::MODE_CUSTOM |
|
326
|
|
|
]; |
|
327
|
|
|
} |
|
328
|
|
|
} |
|
329
|
|
|
|
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.