Completed
Push — master ( 75f235...93cae7 )
by Jared
01:33
created

Query::where()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8497
c 0
b 0
f 0
cc 6
nc 8
nop 3
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @see http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
12
namespace Pulsar;
13
14
use Pulsar\Exception\ModelNotFoundException;
15
use Pulsar\Relation\Relationship;
16
17
/**
18
 * Represents a query against a model type.
19
 */
20
class Query
21
{
22
    const DEFAULT_LIMIT = 100;
23
    const MAX_LIMIT = 1000;
24
25
    /**
26
     * @var Model|string
27
     */
28
    private $model;
29
30
    /**
31
     * @var array
32
     */
33
    private $joins;
34
35
    /**
36
     * @var array
37
     */
38
    private $eagerLoaded;
39
40
    /**
41
     * @var array
42
     */
43
    private $where;
44
45
    /**
46
     * @var int
47
     */
48
    private $limit;
49
50
    /**
51
     * @var int
52
     */
53
    private $start;
54
55
    /**
56
     * @var array
57
     */
58
    private $sort;
59
60
    /**
61
     * @param Model|string $model
62
     */
63
    public function __construct($model = '')
64
    {
65
        $this->model = $model;
66
        $this->joins = [];
67
        $this->eagerLoaded = [];
68
        $this->where = [];
69
        $this->start = 0;
70
        $this->limit = self::DEFAULT_LIMIT;
71
        $this->sort = [];
72
    }
73
74
    /**
75
     * Gets the model class associated with this query.
76
     *
77
     * @return Model|string
78
     */
79
    public function getModel()
80
    {
81
        return $this->model;
82
    }
83
84
    /**
85
     * Sets the limit for this query.
86
     *
87
     * @return $this
88
     */
89
    public function limit(int $limit)
90
    {
91
        $this->limit = min($limit, self::MAX_LIMIT);
92
93
        return $this;
94
    }
95
96
    /**
97
     * Gets the limit for this query.
98
     */
99
    public function getLimit(): int
100
    {
101
        return $this->limit;
102
    }
103
104
    /**
105
     * Sets the start offset.
106
     *
107
     * @return $this
108
     */
109
    public function start(int $start)
110
    {
111
        $this->start = max($start, 0);
112
113
        return $this;
114
    }
115
116
    /**
117
     * Gets the start offset.
118
     */
119
    public function getStart(): int
120
    {
121
        return $this->start;
122
    }
123
124
    /**
125
     * Sets the sort pattern for the query.
126
     *
127
     * @param array|string $sort
128
     *
129
     * @return $this
130
     */
131
    public function sort($sort)
132
    {
133
        $columns = explode(',', $sort);
134
135
        $sortParams = [];
136
        foreach ($columns as $column) {
137
            $c = explode(' ', trim($column));
138
139
            if (2 != count($c)) {
140
                continue;
141
            }
142
143
            // validate direction
144
            $direction = strtolower($c[1]);
145
            if (!in_array($direction, ['asc', 'desc'])) {
146
                continue;
147
            }
148
149
            $sortParams[] = [$c[0], $direction];
150
        }
151
152
        $this->sort = $sortParams;
153
154
        return $this;
155
    }
156
157
    /**
158
     * Gets the sort parameters.
159
     */
160
    public function getSort(): array
161
    {
162
        return $this->sort;
163
    }
164
165
    /**
166
     * Sets the where parameters.
167
     * Accepts the following forms:
168
     *   i)   where(['name' => 'Bob'])
169
     *   ii)  where('name', 'Bob')
170
     *   iii) where('balance', 100, '>')
171
     *   iv)  where('balance > 100').
172
     *
173
     * @param array|string $where
174
     * @param mixed        $value     optional value
175
     * @param string|null  $condition optional condition
176
     *
177
     * @return $this
178
     */
179
    public function where($where, $value = null, $condition = null)
180
    {
181
        // handles i.
182
        if (is_array($where)) {
183
            // only key-value format is accepted in arrays
184
            foreach ($where as $key => $value) {
185
                $this->where($key, $value);
186
            }
187
        } else {
188
            if ($value instanceof Model) {
189
                $value = $value->id();
190
            }
191
192
            // handles iii.
193
            $args = func_num_args();
194
            if ($args > 2) {
195
                $this->where[] = [$where, $value, $condition];
196
            // handles ii.
197
            } elseif (2 == $args) {
198
                $this->where[$where] = $value;
199
            // handles iv.
200
            } else {
201
                $this->where[] = $where;
202
            }
203
        }
204
205
        return $this;
206
    }
207
208
    /**
209
     * Gets the where parameters.
210
     */
211
    public function getWhere(): array
212
    {
213
        return $this->where;
214
    }
215
216
    /**
217
     * Adds a join to the query. Matches a property on this model
218
     * to the ID of the model we are joining.
219
     *
220
     * @param string $model  model being joined
221
     * @param string $column name of local property
222
     *
223
     * @return $this
224
     */
225
    public function join($model, string $column, string $foreignKey)
226
    {
227
        $this->joins[] = [$model, $column, $foreignKey];
228
229
        return $this;
230
    }
231
232
    /**
233
     * Gets the joins.
234
     */
235
    public function getJoins(): array
236
    {
237
        return $this->joins;
238
    }
239
240
    /**
241
     * Marks a relationship property on the model that should be eager loaded.
242
     *
243
     * @param string $k local property containing the relationship
244
     *
245
     * @return $this
246
     */
247
    public function with(string $k)
248
    {
249
        if (!in_array($k, $this->eagerLoaded)) {
250
            $this->eagerLoaded[] = $k;
251
        }
252
253
        return $this;
254
    }
255
256
    /**
257
     * Gets the relationship properties that are going to be eager-loaded.
258
     */
259
    public function getWith(): array
260
    {
261
        return $this->eagerLoaded;
262
    }
263
264
    /**
265
     * Executes the query against the model's driver.
266
     *
267
     * @return Model[] results
268
     */
269
    public function execute(): array
270
    {
271
        // instantiate a model so that initialize() is called and properties are filled in
272
        // otherwise this empty model is not used
273
        $modelClass = $this->model;
274
        $model = new $modelClass();
0 ignored issues
show
Unused Code introduced by
$model is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
275
276
        return Hydrator::hydrate($modelClass::getDriver()->queryModels($this), $modelClass, $this->eagerLoaded);
277
    }
278
279
    /**
280
     * Creates an iterator for a search.
281
     *
282
     * @return Iterator
283
     */
284
    public function all()
285
    {
286
        return new Iterator($this);
287
    }
288
289
    /**
290
     * Finds exactly one model. If zero or more than one are found
291
     * then this function will fail.
292
     *
293
     * @throws ModelNotFoundException when the result is not exactly one model
294
     */
295
    public function one(): Model
296
    {
297
        $models = $this->limit(1)->execute();
298
299
        if (0 == count($models)) {
300
            $model = $this->model;
301
            throw new ModelNotFoundException('Could not find '.$model::modelName());
302
        } elseif (count($models) > 1) {
303
            $model = $this->model;
304
            throw new ModelNotFoundException('Found more than one '.$model::modelName().' when only one result should have been found.');
305
        }
306
307
        return $models[0];
308
    }
309
310
    /**
311
     * Finds exactly one model. If zero or more than one are found
312
     * then this function will return null.
313
     *
314
     * @throws ModelNotFoundException when the result is not exactly one model
315
     */
316
    public function oneOrNull(): ?Model
317
    {
318
        $models = $this->limit(1)->execute();
319
320
        return 1 == count($models) ? $models[0] : null;
321
    }
322
323
    /**
324
     * Executes the query against the model's driver and returns the first result.
325
     *
326
     * @return Model[]
327
     */
328
    public function first(int $limit = 1): array
329
    {
330
        return $this->limit($limit)->execute();
331
    }
332
333
    /**
334
     * Gets the number of models matching the query.
335
     */
336
    public function count(): int
337
    {
338
        $model = $this->model;
339
        $driver = $model::getDriver();
340
341
        return $driver->count($this);
342
    }
343
344
    /**
345
     * Gets the sum of a property matching the query.
346
     *
347
     * @return number
348
     */
349
    public function sum(string $property)
350
    {
351
        $model = $this->model;
352
        $driver = $model::getDriver();
353
354
        return $driver->sum($this, $property);
355
    }
356
357
    /**
358
     * Gets the average of a property matching the query.
359
     *
360
     * @return number
361
     */
362
    public function average(string $property)
363
    {
364
        $model = $this->model;
365
        $driver = $model::getDriver();
366
367
        return $driver->average($this, $property);
368
    }
369
370
    /**
371
     * Gets the max of a property matching the query.
372
     *
373
     * @return number
374
     */
375
    public function max(string $property)
376
    {
377
        $model = $this->model;
378
        $driver = $model::getDriver();
379
380
        return $driver->max($this, $property);
381
    }
382
383
    /**
384
     * Gets the min of a property matching the query.
385
     *
386
     * @return number
387
     */
388
    public function min(string $property)
389
    {
390
        $model = $this->model;
391
        $driver = $model::getDriver();
392
393
        return $driver->min($this, $property);
394
    }
395
396
    /**
397
     * Updates all of the models matched by this query.
398
     *
399
     * @todo should be optimized to be done in a single call to the data layer
400
     *
401
     * @param array $params key-value update parameters
402
     *
403
     * @return int # of models updated
404
     */
405
    public function set(array $params): int
406
    {
407
        $n = 0;
408
        foreach ($this->all() as $model) {
409
            $model->set($params);
410
            ++$n;
411
        }
412
413
        return $n;
414
    }
415
416
    /**
417
     * Deletes all of the models matched by this query.
418
     *
419
     * @todo should be optimized to be done in a single call to the data layer
420
     *
421
     * @return int # of models deleted
422
     */
423
    public function delete(): int
424
    {
425
        $n = 0;
426
        foreach ($this->all() as $model) {
427
            $model->delete();
428
            ++$n;
429
        }
430
431
        return $n;
432
    }
433
}
434