Passed
Push — develop ( 33032a...666b96 )
by Henry
02:03
created

Getters   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 429
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 129
c 1
b 0
f 0
dl 0
loc 429
rs 5.04
wmc 57

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getByQuery() 0 3 1
A getByContext() 0 19 3
A getAllByContextObject() 0 3 1
A getByField() 0 5 1
A buildExtraColumns() 0 9 4
A getByContextObject() 0 3 1
A buildHaving() 0 4 3
A getUniqueHandle() 0 33 4
A getAllRecords() 0 23 4
A getRecordByField() 0 3 1
A getAllByField() 0 3 1
A getAllByContext() 0 14 2
A getByID() 0 5 2
A getAllByWhere() 0 3 1
A instantiateRecord() 0 4 2
A getRecordByWhere() 0 18 4
A getAllByQuery() 0 3 1
A getAll() 0 3 1
A getByHandle() 0 8 3
A generateRandomHandle() 0 7 2
A getTableByQuery() 0 3 1
B getAllRecordsByWhere() 0 51 9
A instantiateRecords() 0 8 2
A getByWhere() 0 5 1
A getAllByClass() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Getters often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Getters, and based on these observations, apply Extract Interface, too.

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
    public static function instantiateRecord($record)
33
    {
34
        $className = static::_getRecordClass($record);
35
        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
    public static function instantiateRecords($records)
45
    {
46
        foreach ($records as &$record) {
47
            $className = static::_getRecordClass($record);
48
            $record = new $className($record);
49
        }
50
51
        return $records;
52
    }
53
54
    public static function getByContextObject(ActiveRecord $Record, $options = [])
55
    {
56
        return static::getByContext($Record::$rootClass, $Record->getPrimaryKeyValue(), $options);
57
    }
58
59
    public static function getByContext($contextClass, $contextID, $options = [])
60
    {
61
        if (!static::fieldExists('ContextClass')) {
62
            throw new Exception('getByContext requires the field ContextClass to be defined');
63
        }
64
65
        $options = Util::prepareOptions($options, [
66
            'conditions' => [],
67
            'order' => false,
68
        ]);
69
70
        $options['conditions']['ContextClass'] = $contextClass;
71
        $options['conditions']['ContextID'] = $contextID;
72
73
        $record = static::getRecordByWhere($options['conditions'], $options);
74
75
        $className = static::_getRecordClass($record);
76
77
        return $record ? new $className($record) : null;
78
    }
79
80
    public static function getByHandle($handle)
81
    {
82
        if (static::fieldExists(static::$handleField)) {
83
            if ($Record = static::getByField(static::$handleField, $handle)) {
84
                return $Record;
85
            }
86
        }
87
        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
    public static function getByID($id)
97
    {
98
        $record = static::getRecordByField(static::$primaryKey ? static::$primaryKey : 'ID', $id, true);
99
100
        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
    public static function getByField($field, $value, $cacheIndex = false)
112
    {
113
        $record = static::getRecordByField($field, $value, $cacheIndex);
114
115
        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
    public static function getRecordByField($field, $value, $cacheIndex = false)
127
    {
128
        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
    public static function getByWhere($conditions, $options = [])
139
    {
140
        $record = static::getRecordByWhere($conditions, $options);
141
142
        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
    public static function getRecordByWhere($conditions, $options = [])
153
    {
154
        if (!is_array($conditions)) {
155
            $conditions = [$conditions];
156
        }
157
158
        $options = Util::prepareOptions($options, [
159
            'order' => false,
160
        ]);
161
162
        // initialize conditions and order
163
        $conditions = static::_mapConditions($conditions);
164
        $order = $options['order'] ? static::_mapFieldOrder($options['order']) : [];
165
166
        return DB::oneRecord(
167
            (new Select())->setTable(static::$tableName)->where(join(') AND (', $conditions))->order($order ? join(',', $order) : '')->limit('1'),
168
            null,
169
            [static::class,'handleException']
170
        );
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
    public static function getByQuery($query, $params = [])
181
    {
182
        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
    public static function getAllByClass($className = false, $options = [])
193
    {
194
        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
    public static function getAllByContextObject(ActiveRecord $Record, $options = [])
205
    {
206
        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
    public static function getAllByContext($contextClass, $contextID, $options = [])
211
    {
212
        if (!static::fieldExists('ContextClass')) {
213
            throw new Exception('getByContext requires the field ContextClass to be defined');
214
        }
215
216
        $options = Util::prepareOptions($options, [
217
            'conditions' => [],
218
        ]);
219
220
        $options['conditions']['ContextClass'] = $contextClass;
221
        $options['conditions']['ContextID'] = $contextID;
222
223
        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
    public static function getAllByField($field, $value, $options = [])
235
    {
236
        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
    public static function getAllByWhere($conditions = [], $options = [])
247
    {
248
        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
    public static function getAll($options = [])
258
    {
259
        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
    public static function getAllRecords($options = [])
269
    {
270
        $options = Util::prepareOptions($options, [
271
            'indexField' => false,
272
            'order' => false,
273
            'limit' => false,
274
            'calcFoundRows' => false,
275
            'offset' => 0,
276
        ]);
277
278
        $select = (new Select())->setTable(static::$tableName)->calcFoundRows();
279
280
        if ($options['order']) {
281
            $select->order(join(',', static::_mapFieldOrder($options['order'])));
282
        }
283
284
        if ($options['limit']) {
285
            $select->limit(sprintf('%u,%u', $options['offset'], $options['limit']));
286
        }
287
        if ($options['indexField']) {
288
            return DB::table(static::_cn($options['indexField']), $select, null, null, [static::class,'handleException']);
289
        } else {
290
            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
    public static function getAllByQuery($query, $params = [])
302
    {
303
        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
    public static function getTableByQuery($keyField, $query, $params = [])
307
    {
308
        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
    public static function getAllRecordsByWhere($conditions = [], $options = [])
319
    {
320
        $className = get_called_class();
321
322
        $options = Util::prepareOptions($options, [
323
            'indexField' => false,
324
            'order' => false,
325
            'limit' => false,
326
            'offset' => 0,
327
            'calcFoundRows' => !empty($options['limit']),
328
            'extraColumns' => false,
329
            'having' => false,
330
        ]);
331
332
        // initialize conditions
333
        if ($conditions) {
334
            if (is_string($conditions)) {
335
                $conditions = [$conditions];
336
            }
337
338
            $conditions = static::_mapConditions($conditions);
339
        }
340
341
        $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
        if ($options['calcFoundRows']) {
343
            $select->calcFoundRows();
344
        }
345
        
346
        $expression = sprintf('`%s`.*', $className::$rootClass);
347
        $select->expression($expression.static::buildExtraColumns($options['extraColumns']));
348
349
        if ($conditions) {
350
            $select->where(join(') AND (', $conditions));
351
        }
352
353
        if ($options['having']) {
354
            $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
        if ($options['order']) {
358
            $select->order(join(',', static::_mapFieldOrder($options['order'])));
359
        }
360
361
        if ($options['limit']) {
362
            $select->limit(sprintf('%u,%u', $options['offset'], $options['limit']));
363
        }
364
365
        if ($options['indexField']) {
366
            return DB::table(static::_cn($options['indexField']), $select, null, null, [static::class,'handleException']);
367
        } else {
368
            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
    public static function getUniqueHandle($text, $options = [])
380
    {
381
        // apply default options
382
        $options = Util::prepareOptions($options, [
383
            'handleField' => static::$handleField,
384
            'domainConstraints' => [],
385
            'alwaysSuffix' => false,
386
            'format' => '%s:%u',
387
        ]);
388
389
        // transliterate accented characters
390
        $text = iconv('UTF-8', 'ASCII//TRANSLIT', $text);
391
392
        // strip bad characters
393
        $handle = $strippedText = preg_replace(
394
            ['/\s+/', '/_*[^a-zA-Z0-9\-_:]+_*/', '/:[-_]/', '/^[-_]+/', '/[-_]+$/'],
395
            ['_', '-', ':', '', ''],
396
            trim($text)
397
        );
398
399
        $handle = trim($handle, '-_');
400
401
        $incarnation = 0;
402
        do {
403
            // TODO: check for repeat posting here?
404
            $incarnation++;
405
406
            if ($options['alwaysSuffix'] || $incarnation > 1) {
407
                $handle = sprintf($options['format'], $strippedText, $incarnation);
408
            }
409
        } while (static::getByWhere(array_merge($options['domainConstraints'], [$options['handleField']=>$handle])));
410
411
        return $handle;
412
    }
413
414
    // TODO: make the handleField
415
    public static function generateRandomHandle($length = 32)
416
    {
417
        do {
418
            $handle = substr(md5(mt_rand(0, mt_getrandmax())), 0, $length);
419
        } while (static::getByField(static::$handleField, $handle));
420
421
        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
    public static function buildExtraColumns($columns)
431
    {
432
        if (!empty($columns)) {
433
            if (is_array($columns)) {
434
                foreach ($columns as $key => $value) {
435
                    return ', '.$value.' AS '.$key;
436
                }
437
            } else {
438
                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
    public static function buildHaving($having)
450
    {
451
        if (!empty($having)) {
452
            return ' (' . (is_array($having) ? join(') AND (', static::_mapConditions($having)) : $having) . ')';
453
        }
454
    }
455
}
456