Order   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 111
dl 0
loc 299
rs 9.6
c 1
b 0
f 0
wmc 35

11 Methods

Rating   Name   Duplication   Size   Complexity  
A values() 0 3 1
A direction() 0 3 1
A mode() 0 3 1
A hasValues() 0 3 1
A defaultData() 0 11 1
A data() 0 11 1
A setDirection() 0 15 4
B setValues() 0 31 7
A setMode() 0 28 5
F setData() 0 59 12
A validModes() 0 8 1
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
        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
        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)) {
0 ignored issues
show
introduced by
The condition is_string($mode) is always true.
Loading history...
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
    public function setDirection($direction)
220
    {
221
        if ($direction === null) {
222
            $this->direction = $direction;
223
            return $this;
224
        }
225
226
        if (!is_string($direction)) {
0 ignored issues
show
introduced by
The condition is_string($direction) is always true.
Loading history...
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)) {
0 ignored issues
show
introduced by
The condition is_array($values) is always true.
Loading history...
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