Completed
Push — master ( 12140e...a3416c )
by Jared
03:31 queued 01:10
created

Query::set()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 1
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
class Query
15
{
16
    const DEFAULT_LIMIT = 100;
17
    const MAX_LIMIT = 1000;
18
19
    /**
20
     * @var string
21
     */
22
    private $model;
23
24
    /**
25
     * @var array
26
     */
27
    private $joins;
28
29
    /**
30
     * @var array
31
     */
32
    private $relationships;
33
34
    /**
35
     * @var array
36
     */
37
    private $where;
38
39
    /**
40
     * @var int
41
     */
42
    private $limit;
43
44
    /**
45
     * @var int
46
     */
47
    private $start;
48
49
    /**
50
     * @var array
51
     */
52
    private $sort;
53
54
    /**
55
     * @param string $model model class
56
     */
57
    public function __construct($model = '')
58
    {
59
        $this->model = $model;
60
        $this->joins = [];
61
        $this->relationships = [];
62
        $this->where = [];
63
        $this->start = 0;
64
        $this->limit = self::DEFAULT_LIMIT;
65
        $this->sort = [];
66
    }
67
68
    /**
69
     * Gets the model class associated with this query.
70
     *
71
     * @return string
72
     */
73
    public function getModel()
74
    {
75
        return $this->model;
76
    }
77
78
    /**
79
     * Sets the limit for this query.
80
     *
81
     * @param int $limit
82
     *
83
     * @return self
84
     */
85
    public function limit($limit)
86
    {
87
        $this->limit = min($limit, self::MAX_LIMIT);
88
89
        return $this;
90
    }
91
92
    /**
93
     * Gets the limit for this query.
94
     *
95
     * @return int
96
     */
97
    public function getLimit()
98
    {
99
        return $this->limit;
100
    }
101
102
    /**
103
     * Sets the start offset.
104
     *
105
     * @param int $start
106
     *
107
     * @return self
108
     */
109
    public function start($start)
110
    {
111
        $this->start = max($start, 0);
112
113
        return $this;
114
    }
115
116
    /**
117
     * Gets the start offset.
118
     *
119
     * @return int
120
     */
121
    public function getStart()
122
    {
123
        return $this->start;
124
    }
125
126
    /**
127
     * Sets the sort pattern for the query.
128
     *
129
     * @param array|string $sort
130
     *
131
     * @return self
132
     */
133
    public function sort($sort)
134
    {
135
        $columns = explode(',', $sort);
136
137
        $sortParams = [];
138
        foreach ($columns as $column) {
139
            $c = explode(' ', trim($column));
140
141
            if (count($c) != 2) {
142
                continue;
143
            }
144
145
            // validate direction
146
            $direction = strtolower($c[1]);
147
            if (!in_array($direction, ['asc', 'desc'])) {
148
                continue;
149
            }
150
151
            $sortParams[] = [$c[0], $direction];
152
        }
153
154
        $this->sort = $sortParams;
155
156
        return $this;
157
    }
158
159
    /**
160
     * Gets the sort parameters.
161
     *
162
     * @return array
163
     */
164
    public function getSort()
165
    {
166
        return $this->sort;
167
    }
168
169
    /**
170
     * Sets the where parameters.
171
     * Accepts the following forms:
172
     *   i)   where(['name' => 'Bob'])
173
     *   ii)  where('name', 'Bob')
174
     *   iii) where('balance', 100, '>')
175
     *   iv)  where('balance > 100').
176
     *
177
     * @param array|string $where
178
     * @param mixed        $value     optional value
179
     * @param string|null  $condition optional condition
180
     *
181
     * @return self
182
     */
183
    public function where($where, $value = null, $condition = null)
184
    {
185
        // handles i.
186
        if (is_array($where)) {
187
            $this->where = array_merge($this->where, $where);
188
        } else {
189
            // handles iii.
190
            $args = func_num_args();
191
            if ($args > 2) {
192
                $this->where[] = [$where, $value, $condition];
193
                // handles ii.
194
            } elseif ($args == 2) {
195
                $this->where[$where] = $value;
196
                // handles iv.
197
            } else {
198
                $this->where[] = $where;
199
            }
200
        }
201
202
        return $this;
203
    }
204
205
    /**
206
     * Gets the where parameters.
207
     *
208
     * @return array
209
     */
210
    public function getWhere()
211
    {
212
        return $this->where;
213
    }
214
215
    /**
216
     * Adds a join to the query. Matches a property on this model
217
     * to the ID of the model we are joining.
218
     *
219
     * @param string $model      model being joined
220
     * @param string $column     name of local property
221
     * @param string $foreignKey
222
     *
223
     * @return self
224
     */
225
    public function join($model, $column, $foreignKey)
226
    {
227
        $this->joins[] = [$model, $column, $foreignKey];
228
229
        return $this;
230
    }
231
232
    /**
233
     * Gets the joins.
234
     *
235
     * @return array
236
     */
237
    public function getJoins()
238
    {
239
        return $this->joins;
240
    }
241
242
    /**
243
     * Marks a relationship property on the model that should be eager loaded.
244
     *
245
     * @param string $k local property containing the relationship
246
     *
247
     * @return self
248
     */
249
    public function with($k)
250
    {
251
        $this->relationships[] = $k;
252
253
        return $this;
254
    }
255
256
    /**
257
     * Gets the relationship properties that are going to be eager-loaded.
258
     *
259
     * @return array
260
     */
261
    public function getWith()
262
    {
263
        return $this->relationships;
264
    }
265
266
    /**
267
     * Executes the query against the model's driver.
268
     *
269
     * @return array results
270
     */
271
    public function execute()
272
    {
273
        $models = [];
274
        $ids = array_fill_keys($this->relationships, []);
275
276
        // fetch the models matching the query
277
        $model = $this->model;
278
        $driver = $model::getDriver();
279
        foreach ($driver->queryModels($this) as $row) {
280
            // get the model's ID
281
            $id = [];
282
            foreach ($model::getIDProperties() as $k) {
283
                $id[] = $row[$k];
284
            }
285
286
            // create the model and cache the loaded values
287
            $models[] = new $model($id, $row);
288
            foreach ($this->relationships as $k) {
289
                if ($row[$k]) {
290
                    $ids[$k][] = $row[$k];
291
                }
292
            }
293
        }
294
295
        // hydrate the eager loaded relationships
296
        if (count($this->relationships) > 0) {
297
            foreach ($this->relationships as $k) {
298
                $property = $model::getProperty($k);
299
                $relationModelClass = $property['relation'];
300
                $relationships = $this->fetchRelationships($relationModelClass, $ids[$k]);
301
302
                foreach ($ids[$k] as $j => $id) {
303
                    if (isset($relationships[$id])) {
304
                        $models[$j]->setRelation($k, $relationships[$id]);
305
                    }
306
                }
307
            }
308
        }
309
310
        return $models;
311
    }
312
313
    /**
314
     * Creates an iterator for a search.
315
     *
316
     * @return Iterator
317
     */
318
    public function all()
319
    {
320
        return new Iterator($this);
321
    }
322
323
    /**
324
     * Executes the query against the model's driver and returns the first result.
325
     *
326
     * @param int $limit
327
     *
328
     * @return array|Model|null when $limit = 1, returns a single model or null, otherwise returns an array
329
     */
330
    public function first($limit = 1)
331
    {
332
        $models = $this->limit($limit)->execute();
333
334
        if ($limit == 1) {
335
            return (count($models) == 1) ? $models[0] : null;
336
        }
337
338
        return $models;
339
    }
340
341
    /**
342
     * Gets the number of models matching the query.
343
     *
344
     * @return int
345
     */
346
    public function count()
347
    {
348
        $model = $this->model;
349
        $driver = $model::getDriver();
350
351
        return $driver->totalRecords($this);
352
    }
353
354
    /**
355
     * @deprecated
356
     *
357
     * Gets the total number of records matching an optional criteria
358
     *
359
     * @param array $where criteria
360
     *
361
     * @return int
362
     */
363
    public function totalRecords(array $where = [])
364
    {
365
        return $this->where($where)->count();
366
    }
367
368
    /**
369
     * Updates all of the models matched by this query.
370
     *
371
     * @param array $params key-value update parameters
372
     *
373
     * @return int # of models updated
374
     */
375
    public function set(array $params)
376
    {
377
        $n = 0;
378
        foreach ($this->all() as $model) {
379
            $model->set($params);
380
            ++$n;
381
        }
382
383
        return $n;
384
    }
385
386
    /**
387
     * Deletes all of the models matched by this query.
388
     *
389
     * @todo should be optimized to be done in a single call to the data layer
390
     *
391
     * @return int # of models deleted
392
     */
393
    public function delete()
394
    {
395
        $n = 0;
396
        foreach ($this->all() as $model) {
397
            $model->delete();
398
            ++$n;
399
        }
400
401
        return $n;
402
    }
403
404
    /**
405
     * Hydrates the eager-loaded relationships for a given set of models.
406
     *
407
     * @param string $modelClass
408
     * @param array  $ids
409
     *
410
     * @return array
411
     */
412
    private function fetchRelationships($modelClass, array $ids)
413
    {
414
        $uniqueIds = array_unique($ids);
415
        if (count($uniqueIds) === 0) {
416
            return [];
417
        }
418
419
        $in = 'id IN ('.implode(',', $uniqueIds).')';
420
        $models = $modelClass::where($in)
421
                             ->first(self::MAX_LIMIT);
422
423
        $result = [];
424
        foreach ($models as $model) {
425
            $result[$model->id()] = $model;
426
        }
427
428
        return $result;
429
    }
430
}
431