Passed
Branch release (bd7374)
by Henry
03:48
created

Getters::getAllByContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 14
ccs 9
cts 9
cp 1
crap 2
rs 10
1
<?php
2
/**
3
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Divergence\Models;
12
13
use Exception;
14
use Divergence\Helpers\Util;
15
use Divergence\Models\ActiveRecord;
16
use Divergence\IO\Database\MySQL as DB;
17
use Divergence\IO\Database\Query\Select;
18
19
/**
20
 * @property string $handleField Defined in the model
21
 * @property string $primaryKey Defined in the model
22
 * @property string $tableName Defined in the model
23
 */
24
trait Getters
25
{
26
    /**
27
     * Converts database record array to a model. Will attempt to use the record's Class field value to as the class to instantiate as or the name of this class if none is provided.
28
     *
29
     * @param array $record Database row as an array.
30
     * @return static|null An instantiated ActiveRecord model from the provided data.
31
     */
32 62
    public static function instantiateRecord($record)
33
    {
34 62
        $className = static::_getRecordClass($record);
35 62
        return $record ? new $className($record) : null;
36
    }
37
38
    /**
39
     * Converts an array of database records to a model corresponding to each record. Will attempt to use the record's Class field value to as the class to instantiate as or the name of this class if none is provided.
40
     *
41
     * @param array $record An array of database rows.
42
     * @return static|null An array of instantiated ActiveRecord models from the provided data.
43
     */
44 29
    public static function instantiateRecords($records)
45
    {
46 29
        foreach ($records as &$record) {
47 29
            $className = static::_getRecordClass($record);
48 29
            $record = new $className($record);
49
        }
50
51 29
        return $records;
52
    }
53
54 1
    public static function getByContextObject(ActiveRecord $Record, $options = [])
55
    {
56 1
        return static::getByContext($Record::$rootClass, $Record->getPrimaryKeyValue(), $options);
57
    }
58
59 2
    public static function getByContext($contextClass, $contextID, $options = [])
60
    {
61 2
        if (!static::fieldExists('ContextClass')) {
62 1
            throw new Exception('getByContext requires the field ContextClass to be defined');
63
        }
64
65 1
        $options = Util::prepareOptions($options, [
66 1
            'conditions' => [],
67 1
            'order' => false,
68 1
        ]);
69
70 1
        $options['conditions']['ContextClass'] = $contextClass;
71 1
        $options['conditions']['ContextID'] = $contextID;
72
73 1
        $record = static::getRecordByWhere($options['conditions'], $options);
74
75 1
        $className = static::_getRecordClass($record);
76
77 1
        return $record ? new $className($record) : null;
78
    }
79
80 14
    public static function getByHandle($handle)
81
    {
82 14
        if (static::fieldExists(static::$handleField)) {
83 9
            if ($Record = static::getByField(static::$handleField, $handle)) {
84 1
                return $Record;
85
            }
86
        }
87 13
        return static::getByID($handle);
88
    }
89
90
    /**
91
     * Get model object by primary key.
92
     *
93
     * @param int $id
94
     * @return static|null
95
     */
96 40
    public static function getByID($id)
97
    {
98 40
        $record = static::getRecordByField(static::$primaryKey ? static::$primaryKey : 'ID', $id, true);
99
100 40
        return static::instantiateRecord($record);
101
    }
102
103
    /**
104
     * Get model object by field.
105
     *
106
     * @param string $field Field name
107
     * @param string $value Field value
108
     * @param boolean $cacheIndex Optional. If we should cache the result or not. Default is false.
109
     * @return static|null
110
     */
111 18
    public static function getByField($field, $value, $cacheIndex = false)
112
    {
113 18
        $record = static::getRecordByField($field, $value, $cacheIndex);
114
115 18
        return static::instantiateRecord($record);
116
    }
117
118
    /**
119
     * Get record by field.
120
     *
121
     * @param string $field Field name
122
     * @param string $value Field value
123
     * @param boolean $cacheIndex Optional. If we should cache the result or not. Default is false.
124
     * @return array|null First database result.
125
     */
126 47
    public static function getRecordByField($field, $value, $cacheIndex = false)
127
    {
128 47
        return static::getRecordByWhere([static::_cn($field) => DB::escape($value)], $cacheIndex);
129
    }
130
131
    /**
132
     * Get the first result instantiated as a model from a simple select query with a where clause you can provide.
133
     *
134
     * @param array|string $conditions If passed as a string a database Where clause. If an array of field/value pairs will convert to a series of `field`='value' conditions joined with an AND operator.
135
     * @param array|string $options Only takes 'order' option. A raw database string that will be inserted into the OR clause of the query or an array of field/direction pairs.
136
     * @return static|null Single model instantiated from the first database result
137
     */
138 23
    public static function getByWhere($conditions, $options = [])
139
    {
140 23
        $record = static::getRecordByWhere($conditions, $options);
141
142 23
        return static::instantiateRecord($record);
143
    }
144
145
    /**
146
     * Get the first result as an array from a simple select query with a where clause you can provide.
147
     *
148
     * @param array|string $conditions If passed as a string a database Where clause. If an array of field/value pairs will convert to a series of `field`='value' conditions joined with an AND operator.
149
     * @param array|string $options Only takes 'order' option. A raw database string that will be inserted into the OR clause of the query or an array of field/direction pairs.
150
     * @return array|null First database result.
151
     */
152 61
    public static function getRecordByWhere($conditions, $options = [])
153
    {
154 61
        if (!is_array($conditions)) {
155 1
            $conditions = [$conditions];
156
        }
157
158 61
        $options = Util::prepareOptions($options, [
159 61
            'order' => false,
160 61
        ]);
161
162
        // initialize conditions and order
163 61
        $conditions = static::_mapConditions($conditions);
164 61
        $order = $options['order'] ? static::_mapFieldOrder($options['order']) : [];
165
166 61
        return DB::oneRecord(
167 61
            (new Select())->setTable(static::$tableName)->where(join(') AND (', $conditions))->order($order ? join(',', $order) : '')->limit('1'),
168 61
            null,
169 61
            [static::class,'handleException']
170 61
        );
171
    }
172
173
    /**
174
     * Get the first result instantiated as a model from a simple select query you can provide.
175
     *
176
     * @param string $query Database query. The passed in string will be passed through vsprintf or sprintf with $params.
177
     * @param array|string $params If an array will be passed through vsprintf as the second parameter with the query as the first. If a string will be used with sprintf instead. If nothing provided you must provide your own query.
178
     * @return static|null Single model instantiated from the first database result
179
     */
180 1
    public static function getByQuery($query, $params = [])
181
    {
182 1
        return static::instantiateRecord(DB::oneRecord($query, $params, [static::class,'handleException']));
183
    }
184
185
    /**
186
     * Get all models in the database by class name. This is a subclass utility method. Requires a Class field on the model.
187
     *
188
     * @param boolean $className The full name of the class including namespace. Optional. Will use the name of the current class if none provided.
189
     * @param array $options
190
     * @return static[]|null Array of instantiated ActiveRecord models returned from the database result.
191
     */
192 1
    public static function getAllByClass($className = false, $options = [])
193
    {
194 1
        return static::getAllByField('Class', $className ? $className : get_called_class(), $options);
0 ignored issues
show
Bug introduced by
It seems like $className ? $className : get_called_class() can also be of type true; however, parameter $value of Divergence\Models\Getters::getAllByField() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

194
        return static::getAllByField('Class', /** @scrutinizer ignore-type */ $className ? $className : get_called_class(), $options);
Loading history...
195
    }
196
197
    /**
198
     * Get all models in the database by passing in an ActiveRecord model which has a 'ContextClass' field by the passed in records primary key.
199
     *
200
     * @param ActiveRecord $Record
201
     * @param array $options
202
     * @return static[]|null Array of instantiated ActiveRecord models returned from the database result.
203
     */
204 1
    public static function getAllByContextObject(ActiveRecord $Record, $options = [])
205
    {
206 1
        return static::getAllByContext($Record::$rootClass, $Record->getPrimaryKeyValue(), $options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::getAllByC...ryKeyValue(), $options) also could return the type Divergence\Models\Getters which is incompatible with the documented return type array<mixed,Divergence\Models\Getters>|null.
Loading history...
207
    }
208
209
210 2
    public static function getAllByContext($contextClass, $contextID, $options = [])
211
    {
212 2
        if (!static::fieldExists('ContextClass')) {
213 1
            throw new Exception('getByContext requires the field ContextClass to be defined');
214
        }
215
216 1
        $options = Util::prepareOptions($options, [
217 1
            'conditions' => [],
218 1
        ]);
219
220 1
        $options['conditions']['ContextClass'] = $contextClass;
221 1
        $options['conditions']['ContextID'] = $contextID;
222
223 1
        return static::instantiateRecords(static::getAllRecordsByWhere($options['conditions'], $options));
224
    }
225
226
    /**
227
     * Get model objects by field and value.
228
     *
229
     * @param string $field Field name
230
     * @param string $value Field value
231
     * @param array $options
232
     * @return static[]|null Array of models instantiated from the database result.
233
     */
234 6
    public static function getAllByField($field, $value, $options = [])
235
    {
236 6
        return static::getAllByWhere([$field => $value], $options);
237
    }
238
239
    /**
240
     * Gets instantiated models as an array from a simple select query with a where clause you can provide.
241
     *
242
     * @param array|string $conditions If passed as a string a database Where clause. If an array of field/value pairs will convert to a series of `field`='value' conditions joined with an AND operator.
243
     * @param array|string $options
244
     * @return static[]|null Array of models instantiated from the database result.
245
     */
246 16
    public static function getAllByWhere($conditions = [], $options = [])
247
    {
248 16
        return static::instantiateRecords(static::getAllRecordsByWhere($conditions, $options));
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::instantia...$conditions, $options)) also could return the type Divergence\Models\Getters which is incompatible with the documented return type array<mixed,Divergence\Models\Getters>|null.
Loading history...
249
    }
250
251
    /**
252
     * Attempts to get all database records for this class and return them as an array of instantiated models.
253
     *
254
     * @param array $options
255
     * @return static[]|null
256
     */
257 10
    public static function getAll($options = [])
258
    {
259 10
        return static::instantiateRecords(static::getAllRecords($options));
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::instantia...etAllRecords($options)) also could return the type Divergence\Models\Getters which is incompatible with the documented return type array<mixed,Divergence\Models\Getters>|null.
Loading history...
260
    }
261
262
    /**
263
     * Attempts to get all database records for this class and returns them as is from the database.
264
     *
265
     * @param array $options
266
     * @return array[]|null
267
     */
268 10
    public static function getAllRecords($options = [])
269
    {
270 10
        $options = Util::prepareOptions($options, [
271 10
            'indexField' => false,
272 10
            'order' => false,
273 10
            'limit' => false,
274 10
            'calcFoundRows' => false,
275 10
            'offset' => 0,
276 10
        ]);
277
278 10
        $select = (new Select())->setTable(static::$tableName)->calcFoundRows();
279
280 10
        if ($options['order']) {
281 6
            $select->order(join(',', static::_mapFieldOrder($options['order'])));
282
        }
283
284 10
        if ($options['limit']) {
285 7
            $select->limit(sprintf('%u,%u', $options['offset'], $options['limit']));
286
        }
287 10
        if ($options['indexField']) {
288
            return DB::table(static::_cn($options['indexField']), $select, null, null, [static::class,'handleException']);
289
        } else {
290 10
            return DB::allRecords($select, null, [static::class,'handleException']);
291
        }
292
    }
293
294
    /**
295
     * Gets all records by a query you provide and then instantiates the results as an array of models.
296
     *
297
     * @param string $query Database query. The passed in string will be passed through vsprintf or sprintf with $params.
298
     * @param array|string $params If an array will be passed through vsprintf as the second parameter with the query as the first. If a string will be used with sprintf instead. If nothing provided you must provide your own query.
299
     * @return static[]|null Array of models instantiated from the first database result
300
     */
301 3
    public static function getAllByQuery($query, $params = [])
302
    {
303 3
        return static::instantiateRecords(DB::allRecords($query, $params, [static::class,'handleException']));
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::instantia...s, 'handleException'))) also could return the type Divergence\Models\Getters which is incompatible with the documented return type array<mixed,Divergence\Models\Getters>|null.
Loading history...
304
    }
305
306 1
    public static function getTableByQuery($keyField, $query, $params = [])
307
    {
308 1
        return static::instantiateRecords(DB::table($keyField, $query, $params, [static::class,'handleException']));
0 ignored issues
show
Bug introduced by
array(static::class, 'handleException') of type array<integer,string> is incompatible with the type string expected by parameter $nullKey of Divergence\IO\Database\MySQL::table(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

308
        return static::instantiateRecords(DB::table($keyField, $query, $params, /** @scrutinizer ignore-type */ [static::class,'handleException']));
Loading history...
309
    }
310
311
    /**
312
     * Gets database results as array from a simple select query with a where clause you can provide.
313
     *
314
     * @param array|string $conditions If passed as a string a database Where clause. If an array of field/value pairs will convert to a series of `field`='value' conditions joined with an AND operator.
315
     * @param array|string $options
316
     * @return array[]|null Array of records from the database result.
317
     */
318 17
    public static function getAllRecordsByWhere($conditions = [], $options = [])
319
    {
320 17
        $className = get_called_class();
321
322 17
        $options = Util::prepareOptions($options, [
323 17
            'indexField' => false,
324 17
            'order' => false,
325 17
            'limit' => false,
326 17
            'offset' => 0,
327 17
            'calcFoundRows' => !empty($options['limit']),
328 17
            'extraColumns' => false,
329 17
            'having' => false,
330 17
        ]);
331
332
        // initialize conditions
333 17
        if ($conditions) {
334 11
            if (is_string($conditions)) {
335 1
                $conditions = [$conditions];
336
            }
337
338 11
            $conditions = static::_mapConditions($conditions);
339
        }
340
341 17
        $select = (new Select())->setTable(static::$tableName)->setTableAlias($className::$rootClass);
0 ignored issues
show
Bug introduced by
The property rootClass does not exist on string.
Loading history...
342 17
        if ($options['calcFoundRows']) {
343 5
            $select->calcFoundRows();
344
        }
345
346 17
        $expression = sprintf('`%s`.*', $className::$rootClass);
347 17
        $select->expression($expression.static::buildExtraColumns($options['extraColumns']));
348
349 17
        if ($conditions) {
350 11
            $select->where(join(') AND (', $conditions));
351
        }
352
353 17
        if ($options['having']) {
354 1
            $select->having(static::buildHaving($options['having']));
0 ignored issues
show
Bug introduced by
It seems like static::buildHaving($options['having']) can also be of type null; however, parameter $having of Divergence\IO\Database\Query\Select::having() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

354
            $select->having(/** @scrutinizer ignore-type */ static::buildHaving($options['having']));
Loading history...
355
        }
356
357 17
        if ($options['order']) {
358 5
            $select->order(join(',', static::_mapFieldOrder($options['order'])));
359
        }
360
361 17
        if ($options['limit']) {
362 3
            $select->limit(sprintf('%u,%u', $options['offset'], $options['limit']));
363
        }
364
365 17
        if ($options['indexField']) {
366
            return DB::table(static::_cn($options['indexField']), $select, null, null, [static::class,'handleException']);
367
        } else {
368 17
            return DB::allRecords($select, null, [static::class,'handleException']);
369
        }
370
    }
371
372
    /**
373
     * Generates a unique string based on the provided text making sure that nothing it returns already exists in the database for the given handleField option. If none is provided the static config $handleField will be used.
374
     *
375
     * @param string $text
376
     * @param array $options
377
     * @return string A unique handle.
378
     */
379 22
    public static function getUniqueHandle($text, $options = [])
380
    {
381
        // apply default options
382 22
        $options = Util::prepareOptions($options, [
383 22
            'handleField' => static::$handleField,
384 22
            'domainConstraints' => [],
385 22
            'alwaysSuffix' => false,
386 22
            'format' => '%s:%u',
387 22
        ]);
388
389
        // transliterate accented characters
390 22
        $text = iconv('UTF-8', 'ASCII//TRANSLIT', $text);
391
392
        // strip bad characters
393 22
        $handle = $strippedText = preg_replace(
394 22
            ['/\s+/', '/_*[^a-zA-Z0-9\-_:]+_*/', '/:[-_]/', '/^[-_]+/', '/[-_]+$/'],
395 22
            ['_', '-', ':', '', ''],
396 22
            trim($text)
397 22
        );
398
399 22
        $handle = trim($handle, '-_');
400
401 22
        $incarnation = 0;
402
        do {
403
            // TODO: check for repeat posting here?
404 22
            $incarnation++;
405
406 22
            if ($options['alwaysSuffix'] || $incarnation > 1) {
407 1
                $handle = sprintf($options['format'], $strippedText, $incarnation);
408
            }
409 22
        } while (static::getByWhere(array_merge($options['domainConstraints'], [$options['handleField']=>$handle])));
410
411 22
        return $handle;
412
    }
413
414
    // TODO: make the handleField
415 1
    public static function generateRandomHandle($length = 32)
416
    {
417
        do {
418 1
            $handle = substr(md5(mt_rand(0, mt_getrandmax())), 0, $length);
419 1
        } while (static::getByField(static::$handleField, $handle));
420
421 1
        return $handle;
422
    }
423
424
    /**
425
     * Builds the extra columns you might want to add to a database select query after the initial list of model fields.
426
     *
427
     * @param array|string $columns An array of keys and values or a string which will be added to a list of fields after the query's SELECT clause.
428
     * @return string|null Extra columns to add after a SELECT clause in a query. Always starts with a comma.
429
     */
430 17
    public static function buildExtraColumns($columns)
431
    {
432 17
        if (!empty($columns)) {
433 1
            if (is_array($columns)) {
434 1
                foreach ($columns as $key => $value) {
435 1
                    return ', '.$value.' AS '.$key;
436
                }
437
            } else {
438 1
                return ', ' . $columns;
439
            }
440
        }
441
    }
442
443
    /**
444
     * Builds the HAVING clause of a MySQL database query.
445
     *
446
     * @param array|string $having Same as conditions. Can provide a string to use or an array of field/value pairs which will be joined by the AND operator.
447
     * @return string|null
448
     */
449 1
    public static function buildHaving($having)
450
    {
451 1
        if (!empty($having)) {
452 1
            return ' (' . (is_array($having) ? join(') AND (', static::_mapConditions($having)) : $having) . ')';
453
        }
454
    }
455
}
456