Passed
Branch release (695ec7)
by Henry
04:31
created

Getters::getAllRecords()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 17
c 1
b 0
f 0
nc 16
nop 1
dl 0
loc 27
rs 9.3888
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
namespace Divergence\Models;
11
12
use Exception;
13
use Divergence\Helpers\Util;
14
use Divergence\Models\ActiveRecord;
15
use Divergence\IO\Database\MySQL as DB;
16
17
/**
18
 * @property string $handleField Defined in the model
19
 * @property string $primaryKey Defined in the model
20
 * @property string $tableName Defined in the model
21
 */
22
trait Getters
23
{
24
    /**
25
     * 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.
26
     *
27
     * @param array $record Database row as an array.
28
     * @return static|null An instantiated ActiveRecord model from the provided data.
29
     */
30
    public static function instantiateRecord($record)
31
    {
32
        $className = static::_getRecordClass($record);
33
        return $record ? new $className($record) : null;
34
    }
35
36
    /**
37
     * 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.
38
     *
39
     * @param array $record An array of database rows.
40
     * @return static|null An array of instantiated ActiveRecord models from the provided data.
41
     */
42
    public static function instantiateRecords($records)
43
    {
44
        foreach ($records as &$record) {
45
            $className = static::_getRecordClass($record);
46
            $record = new $className($record);
47
        }
48
49
        return $records;
50
    }
51
52
    public static function getByContextObject(ActiveRecord $Record, $options = [])
53
    {
54
        return static::getByContext($Record::$rootClass, $Record->getPrimaryKeyValue(), $options);
55
    }
56
57
    public static function getByContext($contextClass, $contextID, $options = [])
58
    {
59
        if (!static::fieldExists('ContextClass')) {
60
            throw new Exception('getByContext requires the field ContextClass to be defined');
61
        }
62
63
        $options = Util::prepareOptions($options, [
64
            'conditions' => [],
65
            'order' => false,
66
        ]);
67
68
        $options['conditions']['ContextClass'] = $contextClass;
69
        $options['conditions']['ContextID'] = $contextID;
70
71
        $record = static::getRecordByWhere($options['conditions'], $options);
72
73
        $className = static::_getRecordClass($record);
74
75
        return $record ? new $className($record) : null;
76
    }
77
78
    public static function getByHandle($handle)
79
    {
80
        if (static::fieldExists(static::$handleField)) {
81
            if ($Record = static::getByField(static::$handleField, $handle)) {
82
                return $Record;
83
            }
84
        }
85
        return static::getByID($handle);
86
    }
87
88
    /**
89
     * Get model object by primary key.
90
     *
91
     * @param int $id
92
     * @return static|null
93
     */
94
    public static function getByID($id)
95
    {
96
        $record = static::getRecordByField(static::$primaryKey ? static::$primaryKey : 'ID', $id, true);
97
98
        return static::instantiateRecord($record);
99
    }
100
101
    /**
102
     * Get model object by field.
103
     *
104
     * @param string $field Field name
105
     * @param string $value Field value
106
     * @param boolean $cacheIndex Optional. If we should cache the result or not. Default is false.
107
     * @return static|null
108
     */
109
    public static function getByField($field, $value, $cacheIndex = false)
110
    {
111
        $record = static::getRecordByField($field, $value, $cacheIndex);
112
113
        return static::instantiateRecord($record);
114
    }
115
116
    /**
117
     * Get record by field.
118
     *
119
     * @param string $field Field name
120
     * @param string $value Field value
121
     * @param boolean $cacheIndex Optional. If we should cache the result or not. Default is false.
122
     * @return array|null First database result.
123
     */
124
    public static function getRecordByField($field, $value, $cacheIndex = false)
125
    {
126
        $query = 'SELECT * FROM `%s` WHERE `%s` = "%s" LIMIT 1';
127
        $params = [
128
            static::$tableName,
129
            static::_cn($field),
130
            DB::escape($value),
131
        ];
132
133
        if ($cacheIndex) {
134
            $key = sprintf('%s/%s:%s', static::$tableName, $field, $value);
135
            return DB::oneRecordCached($key, $query, $params, [static::class,'handleError']);
136
        } else {
137
            return DB::oneRecord($query, $params, [static::class,'handleError']);
138
        }
139
    }
140
141
    /**
142
     * Get the first result instantiated as a model from a simple select query with a where clause you can provide.
143
     *
144
     * @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.
145
     * @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.
146
     * @return static|null Single model instantiated from the first database result
147
     */
148
    public static function getByWhere($conditions, $options = [])
149
    {
150
        $record = static::getRecordByWhere($conditions, $options);
151
152
        return static::instantiateRecord($record);
153
    }
154
155
    /**
156
     * Get the first result as an array from a simple select query with a where clause you can provide.
157
     *
158
     * @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.
159
     * @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.
160
     * @return array|null First database result.
161
     */
162
    public static function getRecordByWhere($conditions, $options = [])
163
    {
164
        if (!is_array($conditions)) {
165
            $conditions = [$conditions];
166
        }
167
168
        $options = Util::prepareOptions($options, [
169
            'order' => false,
170
        ]);
171
172
        // initialize conditions and order
173
        $conditions = static::_mapConditions($conditions);
174
        $order = $options['order'] ? static::_mapFieldOrder($options['order']) : [];
175
176
        return DB::oneRecord(
177
            'SELECT * FROM `%s` WHERE (%s) %s LIMIT 1',
178
            [
179
                static::$tableName,
180
                join(') AND (', $conditions),
181
                $order ? 'ORDER BY '.join(',', $order) : '',
182
            ],
183
            [static::class,'handleError']
184
        );
185
    }
186
187
    /**
188
     * Get the first result instantiated as a model from a simple select query you can provide.
189
     *
190
     * @param string $query Database query. The passed in string will be passed through vsprintf or sprintf with $params.
191
     * @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.
192
     * @return static|null Single model instantiated from the first database result
193
     */
194
    public static function getByQuery($query, $params = [])
195
    {
196
        return static::instantiateRecord(DB::oneRecord($query, $params, [static::class,'handleError']));
197
    }
198
199
    /**
200
     * Get all models in the database by class name. This is a subclass utility method. Requires a Class field on the model.
201
     *
202
     * @param boolean $className The full name of the class including namespace. Optional. Will use the name of the current class if none provided.
203
     * @param array $options
204
     * @return static[]|null Array of instantiated ActiveRecord models returned from the database result.
205
     */
206
    public static function getAllByClass($className = false, $options = [])
207
    {
208
        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

208
        return static::getAllByField('Class', /** @scrutinizer ignore-type */ $className ? $className : get_called_class(), $options);
Loading history...
209
    }
210
211
    /**
212
     * Get all models in the database by passing in an ActiveRecord model which has a 'ContextClass' field by the passed in records primary key.
213
     *
214
     * @param ActiveRecord $Record
215
     * @param array $options
216
     * @return static[]|null Array of instantiated ActiveRecord models returned from the database result.
217
     */
218
    public static function getAllByContextObject(ActiveRecord $Record, $options = [])
219
    {
220
        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...
221
    }
222
223
224
    public static function getAllByContext($contextClass, $contextID, $options = [])
225
    {
226
        if (!static::fieldExists('ContextClass')) {
227
            throw new Exception('getByContext requires the field ContextClass to be defined');
228
        }
229
230
        $options = Util::prepareOptions($options, [
231
            'conditions' => [],
232
        ]);
233
234
        $options['conditions']['ContextClass'] = $contextClass;
235
        $options['conditions']['ContextID'] = $contextID;
236
237
        return static::instantiateRecords(static::getAllRecordsByWhere($options['conditions'], $options));
238
    }
239
240
    /**
241
     * Get model objects by field and value.
242
     *
243
     * @param string $field Field name
244
     * @param string $value Field value
245
     * @param array $options
246
     * @return static[]|null Array of models instantiated from the database result.
247
     */
248
    public static function getAllByField($field, $value, $options = [])
249
    {
250
        return static::getAllByWhere([$field => $value], $options);
251
    }
252
253
    /**
254
     * Gets instantiated models as an array from a simple select query with a where clause you can provide.
255
     *
256
     * @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.
257
     * @param array|string $options
258
     * @return static[]|null Array of models instantiated from the database result.
259
     */
260
    public static function getAllByWhere($conditions = [], $options = [])
261
    {
262
        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...
263
    }
264
265
    /**
266
     * Attempts to get all database records for this class and return them as an array of instantiated models.
267
     *
268
     * @param array $options
269
     * @return static[]|null
270
     */
271
    public static function getAll($options = [])
272
    {
273
        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...
274
    }
275
276
    /**
277
     * Attempts to get all database records for this class and returns them as is from the database.
278
     *
279
     * @param array $options
280
     * @return array[]|null
281
     */
282
    public static function getAllRecords($options = [])
283
    {
284
        $options = Util::prepareOptions($options, [
285
            'indexField' => false,
286
            'order' => false,
287
            'limit' => false,
288
            'calcFoundRows' => false,
289
            'offset' => 0,
290
        ]);
291
292
        $query = 'SELECT '.($options['calcFoundRows'] ? 'SQL_CALC_FOUND_ROWS' : '').'* FROM `%s`';
293
        $params = [
294
            static::$tableName,
295
        ];
296
297
        if ($options['order']) {
298
            $query .= ' ORDER BY ' . join(',', static::_mapFieldOrder($options['order']));
299
        }
300
301
        if ($options['limit']) {
302
            $query .= sprintf(' LIMIT %u,%u', $options['offset'], $options['limit']);
303
        }
304
305
        if ($options['indexField']) {
306
            return DB::table(static::_cn($options['indexField']), $query, $params, null, [static::class,'handleError']);
307
        } else {
308
            return DB::allRecords($query, $params, [static::class,'handleError']);
309
        }
310
    }
311
312
    /**
313
     * Gets all records by a query you provide and then instantiates the results as an array of models.
314
     *
315
     * @param string $query Database query. The passed in string will be passed through vsprintf or sprintf with $params.
316
     * @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.
317
     * @return static[]|null Array of models instantiated from the first database result
318
     */
319
    public static function getAllByQuery($query, $params = [])
320
    {
321
        return static::instantiateRecords(DB::allRecords($query, $params, [static::class,'handleError']));
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::instantia...class, 'handleError'))) also could return the type Divergence\Models\Getters which is incompatible with the documented return type array<mixed,Divergence\Models\Getters>|null.
Loading history...
322
    }
323
324
    public static function getTableByQuery($keyField, $query, $params = [])
325
    {
326
        return static::instantiateRecords(DB::table($keyField, $query, $params, [static::class,'handleError']));
0 ignored issues
show
Bug introduced by
array(static::class, 'handleError') 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

326
        return static::instantiateRecords(DB::table($keyField, $query, $params, /** @scrutinizer ignore-type */ [static::class,'handleError']));
Loading history...
327
    }
328
329
    /**
330
     * Gets database results as array from a simple select query with a where clause you can provide.
331
     *
332
     * @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.
333
     * @param array|string $options
334
     * @return array[]|null Array of records from the database result.
335
     */
336
    public static function getAllRecordsByWhere($conditions = [], $options = [])
337
    {
338
        $className = get_called_class();
339
340
        $options = Util::prepareOptions($options, [
341
            'indexField' => false,
342
            'order' => false,
343
            'limit' => false,
344
            'offset' => 0,
345
            'calcFoundRows' => !empty($options['limit']),
346
            'extraColumns' => false,
347
            'having' => false,
348
        ]);
349
350
        // initialize conditions
351
        if ($conditions) {
352
            if (is_string($conditions)) {
353
                $conditions = [$conditions];
354
            }
355
356
            $conditions = static::_mapConditions($conditions);
357
        }
358
359
        // build query
360
        $query  = 'SELECT %1$s `%3$s`.*';
361
        $query .= static::buildExtraColumns($options['extraColumns']);
362
        $query .= ' FROM `%2$s` AS `%3$s`';
363
        $query .= ' WHERE (%4$s)';
364
365
        $query .= static::buildHaving($options['having']);
366
367
        $params = [
368
            $options['calcFoundRows'] ? 'SQL_CALC_FOUND_ROWS' : '',
369
            static::$tableName,
370
            $className::$rootClass,
371
            $conditions ? join(') AND (', $conditions) : '1',
372
        ];
373
374
        if ($options['order']) {
375
            $query .= ' ORDER BY ' . join(',', static::_mapFieldOrder($options['order']));
376
        }
377
378
        if ($options['limit']) {
379
            $query .= sprintf(' LIMIT %u,%u', $options['offset'], $options['limit']);
380
        }
381
382
        if ($options['indexField']) {
383
            return DB::table(static::_cn($options['indexField']), $query, $params, null, [static::class,'handleError']);
384
        } else {
385
            return DB::allRecords($query, $params, [static::class,'handleError']);
386
        }
387
    }
388
389
    /**
390
     * 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.
391
     *
392
     * @param string $text
393
     * @param array $options
394
     * @return string A unique handle.
395
     */
396
    public static function getUniqueHandle($text, $options = [])
397
    {
398
        // apply default options
399
        $options = Util::prepareOptions($options, [
400
            'handleField' => static::$handleField,
401
            'domainConstraints' => [],
402
            'alwaysSuffix' => false,
403
            'format' => '%s:%u',
404
        ]);
405
406
        // transliterate accented characters
407
        $text = iconv('UTF-8', 'ASCII//TRANSLIT', $text);
408
409
        // strip bad characters
410
        $handle = $strippedText = preg_replace(
411
            ['/\s+/', '/_*[^a-zA-Z0-9\-_:]+_*/', '/:[-_]/', '/^[-_]+/', '/[-_]+$/'],
412
            ['_', '-', ':', '', ''],
413
            trim($text)
414
        );
415
416
        $handle = trim($handle, '-_');
417
418
        $incarnation = 0;
419
        do {
420
            // TODO: check for repeat posting here?
421
            $incarnation++;
422
423
            if ($options['alwaysSuffix'] || $incarnation > 1) {
424
                $handle = sprintf($options['format'], $strippedText, $incarnation);
425
            }
426
        } while (static::getByWhere(array_merge($options['domainConstraints'], [$options['handleField']=>$handle])));
427
428
        return $handle;
429
    }
430
}
431