• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rotexsoft / leanorm / 23474882386

24 Mar 2026 05:37AM UTC coverage: 96.042% (+0.2%) from 95.797%
23474882386

push

github

rotexdegba
Minimum PHP 8.2 refactoring

1650 of 1718 relevant lines covered (96.04%)

188.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.87
/src/LeanOrm/Model/CommonRecordCodeTrait.php
1
<?php
2
declare(strict_types=1);
3
namespace LeanOrm\Model;
4

5
/**
6
 * This trait contains code shared between ReadOnlyRecord & Record
7
 * 
8
 * @author Rotimi Adegbamigbe
9
 * @copyright (c) 2026, Rotexsoft
10
 */
11
trait CommonRecordCodeTrait {
12
    
13
    public const RELATED_DATA_NOT_LOADED_OR_EXISTENT_MSG = "Data either not loaded or doesn't exist in the database";
14
    
15
    /**
16
     * Data for this record ([to be saved to the db] or [as read from the db]).
17
     */
18
    protected array $data = [];
19
    
20
    /**
21
     * Data for this record (not to be saved to the db i.e. not from any actual db column and not related data).
22
     */
23
    protected array $non_table_col_and_non_related_data = [];
24
    
25
    /**
26
     * Holds relationship data retrieved based on definitions in the array below.
27
     * \GDAO\Model::$relations
28
     */
29
    protected array $related_data = [];
30

31
    /**
32
     * The model object that saves and reads data to and from the db on behalf 
33
     * of this record.
34
     */
35
    protected \GDAO\Model $model;
36
    
37
    /**
38
     * 
39
     * @param array $data associative array of data to be loaded into this record.
40
     *                    [
41
     *                      'col_name1'=>'value_for_col1', 
42
     *                      .............................,
43
     *                      .............................,
44
     *                      'col_nameN'=>'value_for_colN'
45
     *                    ]
46
     * @param \GDAO\Model $model The model object that transfers data between the db and this record.
47
     */
48
    public function __construct(array $data, \GDAO\Model $model) {
49
        
50
        $this->setModel($model);
656✔
51
        $this->loadData($data);
656✔
52
    }
53
    
54
    public function __destruct() {
55

56
        //print "Destroying Record with Primary key Value: " . $this->getPrimaryVal() . "<br>";
57
        unset($this->data);
656✔
58

59
        if(property_exists($this, 'initial_data')) {
656✔
60

61
            /** @psalm-suppress UndefinedThisPropertyFetch */
62
            unset($this->initial_data);
564✔
63
        }
64

65
        if(property_exists($this, 'is_new')) {
656✔
66

67
            /** @psalm-suppress UndefinedThisPropertyFetch */
68
            unset($this->is_new);
564✔
69
        }
70

71
        unset($this->non_table_col_and_non_related_data);
656✔
72
        unset($this->related_data);
656✔
73
        //Don't unset $this->model, it may still be referenced by other 
74
        //Record and / or Collection objects.
75
    }
76
    
77
    /**
78
     * Get the data for this record.
79
     * Modifying the returned data will not affect the data inside this record.
80
     * 
81
     * @return array a copy of the current data for this record
82
     */
83
    public function getData(): array {
84
        
85
        return $this->data;
224✔
86
    }
87
    
88
    /**
89
     * Get the data contained in an instance of the record class and all
90
     * loaded related data for the record. Related data that was not loaded
91
     * for a record before calling this method will not be loaded even if it
92
     * exists in the database, it will be marked as not loaded or not existent.
93
     */
94
    public function getDataAndRelatedData(): array {
95

96
        $data = $this->data; // take a copy of the db table data for this record
8✔
97

98
        /** @psalm-suppress MixedAssignment */
99
        foreach ($this->getModel()->getRelationNames() as $relation) {
8✔
100

101
            /** @psalm-suppress MixedArrayOffset */
102
            if(isset($this->related_data[$relation])) {
8✔
103

104
                // Make sure the related data was load before a call to this method
105
                if(
106
                    $this->related_data[$relation] instanceof Record
8✔
107
                    || $this->related_data[$relation] instanceof ReadOnlyRecord
8✔
108
                    || $this->related_data[$relation] instanceof Collection
8✔
109
                ) {
110
                    /** @psalm-suppress MixedArrayOffset */
111
                    $data[$relation] = $this->related_data[$relation]->getDataAndRelatedData();
8✔
112

113
                } elseif(\is_array($this->related_data[$relation])) {
8✔
114

115
                    /** @psalm-suppress MixedArrayOffset */
116
                    $data[$relation] = [];
8✔
117

118
                    foreach ($this->related_data[$relation] as $record) {
8✔
119

120
                        if(
121
                            $record instanceof Record
×
122
                            || $record  instanceof ReadOnlyRecord
×
123
                        ) {
124
                            // definitely an array of records
125
                            /** @psalm-suppress MixedArrayOffset */
126
                            $data[$relation][] = $record->getDataAndRelatedData();
×
127

128
                        } else {
129

130
                            /** @psalm-suppress MixedArrayOffset */
131
                            $data[$relation][] = $record;
×
132
                        }
133
                    }
134

135
                } else {
136

137
                    /** @psalm-suppress MixedArrayOffset */
138
                    $data[$relation] = $this->related_data[$relation];
×
139
                }
140

141
            } else {
142

143
                // Related data was not loaded before this method was called
144
                // Either the data exists in the db but was not eager loaded
145
                // or the data does not exist.
146
                /** @psalm-suppress MixedArrayOffset */
147
                $data[$relation] = static::RELATED_DATA_NOT_LOADED_OR_EXISTENT_MSG;
8✔
148

149
            } // if(isset($this->related_data[$relation]))
150
        } // foreach ($this->getModel()->getRelationNames() as $relation)
151

152
        return $data;
8✔
153
    }
154
    
155
    /**
156
     * Get all the related data loaded into this record.
157
     * Modifying the returned data will not affect the related data inside this record.
158
     * 
159
     * @return array a reference to all the related data loaded into this record.
160
     */
161
    public function getRelatedData(): array {
162
        
163
        return $this->related_data;
44✔
164
    }
165
    
166
    /**
167
     * Get data for this record that does not belong to any of it's table columns and is not related data.
168
     * 
169
     * @return array Data for this record (not to be saved to the db i.e. not from any actual db column and not related data).
170
     */
171
    public function getNonTableColAndNonRelatedData(): array {
172
        
173
        return $this->non_table_col_and_non_related_data;
52✔
174
    }
175
    
176
    /**
177
     * Get a reference to the data for this record.
178
     * Modifying the returned data will affect the data inside this record.
179
     * 
180
     * @return array a reference to the current data for this record.
181
     */
182
    public function &getDataByRef(): array {
183
        
184
        return $this->data;
8✔
185
    }
186
    
187
    /**
188
     * Get a reference to all the related data loaded into this record.
189
     * Modifying the returned data will affect the related data inside this record.
190
     * 
191
     * @return array a reference to all the related data loaded into this record.
192
     */
193
    public function &getRelatedDataByRef(): array {
194
        
195
        return $this->related_data;
4✔
196
    }
197
    
198
    /**
199
     * Get data for this record that does not belong to any of it's table columns and is not related data.
200
     * 
201
     * @return array reference to the data for this record (not from any actual db column and not related data).
202
     */
203
    public function &getNonTableColAndNonRelatedDataByRef(): array {
204
        
205
        return $this->non_table_col_and_non_related_data;
4✔
206
    }
207

208
    /**
209
     * Set relation data for this record.
210
     * 
211
     * @param string $key relation name
212
     * @param mixed $value an array or record or collection containing related data
213
     * 
214
     * @throws \GDAO\Model\RecordRelationWithSameNameAsAnExistingDBTableColumnNameException
215
     */
216
    public function setRelatedData(string $key, array|\GDAO\Model\RecordInterface|\GDAO\Model\CollectionInterface $value): static {
217
        
218
        $my_model = $this->getModel();
160✔
219
        $table_cols = $my_model->getTableColNames();
160✔
220
        
221
        if( in_array($key, $table_cols) ) {
160✔
222
            
223
            //Error trying to add a relation whose name collides with an actual
224
            //name of a column in the db table associated with this record's model.
225
            $msg = sprintf("ERROR: You cannont add a relationship with the name '%s' ", $key)
4✔
226
                 . " to the record (".$this::class."). The database table "
4✔
227
                 . sprintf(" '%s' associated with the ", $my_model->getTableName())
4✔
228
                 . " record's model (".$my_model::class.") already contains"
4✔
229
                 . " a column with the same name."
4✔
230
                 . PHP_EOL . $this::class . '::' . __FUNCTION__ . '(...).' 
4✔
231
                 . PHP_EOL;
4✔
232
                 
233
            throw new \GDAO\Model\RecordRelationWithSameNameAsAnExistingDBTableColumnNameException($msg);
4✔
234
        }
235
        
236
        //We're safe, set the related data.
237
        $this->related_data[$key] = $value;
156✔
238
        
239
        return $this;
156✔
240
    }
241
    
242
    /**
243
     * Get the model object that saves and reads data to and from the db on 
244
     * behalf of this record
245
     */
246
    public function getModel(): \GDAO\Model {
247
        
248
        return $this->model;
656✔
249
    }
250
    
251
    /**
252
     * @return string name of the primary-key column of the db table this record belongs to
253
     */
254
    public function getPrimaryCol(): string {
255

256
        return $this->getModel()->getPrimaryCol();
112✔
257
    }
258

259
    /**
260
     * @return mixed the value stored in the primary-key column for this record.
261
     */
262
    public function getPrimaryVal(): mixed {
263

264
        return $this->{$this->getPrimaryCol()};
108✔
265
    }
266
    
267
    /**
268
     * Set the \GDAO\Model object for this record
269
     * 
270
     * @param \GDAO\Model $model A model object that will be used by this record to communicate with the DB
271
     */
272
    public function setModel(\GDAO\Model $model): static {
273
        
274
        $this->model = $model;
656✔
275
        
276
        return $this;
656✔
277
    }
278
    
279
    /**
280
     * Get all the data and property (name & value pairs) for this record.
281
     * 
282
     * @return array of all data & property (name & value pairs) for this record.
283
     */
284
    public function toArray(): array {
285

286
        $array = get_object_vars($this);
40✔
287
        
288
        // remove the model object, it can be fetched via $this->getModel() if needed
289
        unset($array['model']);
40✔
290
        
291
        return $array;
40✔
292
    }
293
    
294
    //Interface Methods
295

296
    /**
297
     * ArrayAccess: does the requested key exist?
298
     * 
299
     * @param string $key The requested key.
300
     */
301
    public function offsetExists($key): bool {
302

303
        return $this->__isset($key);
52✔
304
    }
305

306
    /**
307
     * ArrayAccess: get a key value.
308
     * 
309
     * @param string $key The requested key.
310
     */
311
    public function offsetGet($key): mixed {
312

313
        return $this->__get($key);
128✔
314
    }
315

316
    /**
317
     * ArrayAccess: set a key value.
318
     * 
319
     * @param string $key The requested key.
320
     * 
321
     * @param string $val The value to set it to.
322
     */
323
    public function offsetSet($key, $val): void {
324

325
        $this->__set($key, $val);
16✔
326
    }
327

328
    /**
329
     * ArrayAccess: unset a key.
330
     * 
331
     * @param string $key The requested key.
332
     */
333
    public function offsetUnset($key): void {
334

335
        $this->__unset($key);
44✔
336
    }
337

338
    /**
339
     * Countable: how many keys are there?
340
     */
341
    public function count(): int {
342

343
        return count($this->data);
32✔
344
    }
345

346
    public function getIterator(): \ArrayIterator {
347

348
        return new \ArrayIterator($this->data + $this->related_data + $this->non_table_col_and_non_related_data);
16✔
349
    }
350
    
351
    //Magic Methods
352
    
353
    /**
354
     * Gets a data value.
355
     * 
356
     * @param string $key The requested data key.
357
     * 
358
     * @return mixed The data value.
359
     */
360
    public function __get($key): mixed {
361

362
        if ( array_key_exists($key, $this->data) ) {
256✔
363
            
364
            return $this->data[$key];
244✔
365
            
366
        } elseif ( array_key_exists($key, $this->related_data) ) {
212✔
367

368
            return $this->related_data[$key];
160✔
369
            
370
        } elseif ( array_key_exists($key, $this->non_table_col_and_non_related_data) ) { 
164✔
371
            
372
            return $this->non_table_col_and_non_related_data[$key];
36✔
373
            
374
        } elseif ( 
375
            $this->getModel() instanceof \GDAO\Model 
152✔
376
            && in_array($key, $this->getModel()->getTableColNames())
152✔
377
        ) {
378
            //$key is a valid db column in the db table assoiciated with this 
379
            //model's record. Initialize it to a null value since it has not 
380
            //yet been set.
381
            $this->data[$key] = null;
88✔
382
            
383
            return $this->data[$key];
88✔
384
            
385
        } elseif(
386
            $this->getModel() instanceof \GDAO\Model 
120✔
387
            && in_array($key, $this->getModel()->getRelationNames()) 
120✔
388
        ) {
389
            if($this->data !== []) {
104✔
390
                //$key is a valid relation name in the model for this record but the 
391
                //related data needs to be loaded for this particular record.
392
                /** @psalm-suppress UndefinedMethod */
393
                $this->getModel()->loadRelationshipData($key, $this, true, true);
96✔
394
                
395
            } else {
396
               
397
                // $this->related_data[$key] === [], meaning we can't fetch related data
398
                $this->related_data[$key] = null;
16✔
399
            }
400
            
401
            //return loaded data
402
            return $this->related_data[$key];
104✔
403
            
404
        } else {
405

406
            //$key is not a valid db column name or relation name.
407
            $msg = sprintf("Property '%s' does not exist in ", $key) 
24✔
408
                   . $this::class . PHP_EOL . $this->__toString();
24✔
409
            
410
            throw new \LeanOrm\Exceptions\Model\NoSuchPropertyForRecordException($msg);
24✔
411
        }
412
    }
413
    
414
    /**
415
     * Does a certain key exist in the data?
416
     * 
417
     * Note that this is slightly different from normal PHP isset(); it will
418
     * say the key is set, even if the key value is null or otherwise empty.
419
     * 
420
     * @param string $key The requested data key.
421
     */
422
    public function __isset($key): bool {
423
        
424
        try { $this->$key;  } //access the property first to make sure the data is loaded
56✔
425
        catch ( \Exception ) {  } //do nothing if exception was thrown
16✔
426
        
427
        return array_key_exists($key, $this->data) 
56✔
428
            || array_key_exists($key, $this->related_data)
56✔
429
            || array_key_exists($key, $this->non_table_col_and_non_related_data);
56✔
430
    }
431
    
432
    /**
433
     * Get the string representation of all the data and property 
434
     * (name & value pairs) for this record.
435
     * 
436
     * @return string string representation of all the data & property 
437
     *                (name & value pairs) for this record.
438
     */
439
    public function __toString(): string {
440
        
441
        return var_export($this->toArray(), true);
28✔
442
    }
443
    
444
    protected function injectData(\GDAO\Model\RecordInterface|array $data_2_load, array $cols_2_load = []): static {
445
        
446
        if (
447
            $data_2_load instanceof \GDAO\Model\RecordInterface
656✔
448
            && $data_2_load->getModel()->getTableName() !== $this->getModel()->getTableName()
656✔
449
        ) {
450
            //Cannot load data
451
            //2 records whose models are associated with different db tables.
452
            //Can't load data, schemas don't match.
453
            $msg = "ERROR: Can't load data from an instance of '".$data_2_load::class
8✔
454
                   . "' into an instance of '".$this::class."'. Their models"
8✔
455
                   . "'".$data_2_load->getModel()::class."' and '"
8✔
456
                   . $this->getModel()::class."' are associated with different"
8✔
457
                   . " db tables ('".$data_2_load->getModel()->getTableName() 
8✔
458
                   ."' and '". $this->getModel()->getTableName()."')."
8✔
459
                   . PHP_EOL . "Unloaded Data:"
8✔
460
                   . PHP_EOL . var_export($data_2_load->getData(), true)  . PHP_EOL;
8✔
461
            
462
            throw new \GDAO\Model\LoadingDataFromInvalidSourceIntoRecordException($msg);
8✔
463
        }
464

465
        $table_col_names_4_my_model = $this->getModel()->getTableColNames();
656✔
466
        
467
        if ( $cols_2_load === [] ) {
656✔
468

469
            if ( is_array($data_2_load) && $data_2_load !== [] ) {
656✔
470

471
                /** @psalm-suppress MixedAssignment */
472
                foreach( $data_2_load as $col_name => $value_2_load ) {
512✔
473
                    
474
                    if ( in_array($col_name, $table_col_names_4_my_model) ) {
512✔
475
                        
476
                        $this->data[$col_name] = $value_2_load;
512✔
477
                        
478
                    } else {
479
                        
480
                        $this->non_table_col_and_non_related_data[$col_name] = $value_2_load;
284✔
481
                    }
482
                }
483
                
484
            } elseif ($data_2_load instanceof \GDAO\Model\RecordInterface) {
224✔
485

486
                $this->data = $data_2_load->getData();
8✔
487
                $this->non_table_col_and_non_related_data = $data_2_load->getNonTableColAndNonRelatedData();
8✔
488
            }
489
            
490
        } else {
491

492
            /** @psalm-suppress MixedAssignment */
493
            foreach ( $cols_2_load as $col_name ) {
8✔
494

495
                /**
496
                 * @psalm-suppress MixedArgument
497
                 * @psalm-suppress MixedArrayOffset
498
                 */
499
                if (
500
                    (
501
                        is_array($data_2_load)
8✔
502
                        && $data_2_load !== []
8✔
503
                        && array_key_exists($col_name, $data_2_load)
8✔
504
                    ) 
505
                    || (
506
                        $data_2_load instanceof \GDAO\Model\RecordInterface 
8✔
507
                        && isset($data_2_load[$col_name])
8✔
508
                    )
509
                ) {
510
                    if ( in_array($col_name, $table_col_names_4_my_model) ) {
8✔
511

512
                        $this->data[$col_name] = $data_2_load[$col_name];
8✔
513

514
                    } else {
515

516
                        $this->non_table_col_and_non_related_data[$col_name] = $data_2_load[$col_name];
8✔
517
                    }
518
                }
519
            } // foreach ( $cols_2_load as $col_name )
520
        }// elseif ( is_array($cols_2_load) && $cols_2_load !== [] )
521
        
522
        return $this;
656✔
523
    }
524

525
    ///////////////////////////////
526
    // Metadata retreiving methods.
527
    ///////////////////////////////
528

529
    /**
530
     * @psalm-suppress PossiblyUnusedMethod
531
     */
532
    public function isAutoIncrementingField(string $fieldName): bool {
533

534
        /** @psalm-suppress UndefinedMethod */
535
        return $this->getModel()->isAutoIncrementingField($fieldName);
8✔
536
    }
537

538
    /**
539
     * @psalm-suppress PossiblyUnusedMethod
540
     */
541
    public function isPrimaryKeyField(string $fieldName): bool {
542

543
        /** @psalm-suppress UndefinedMethod */
544
        return $this->getModel()->isPrimaryKeyField($fieldName);
8✔
545
    }
546

547
    /**
548
     * @psalm-suppress PossiblyUnusedMethod
549
     */
550
    public function isRequiredField(string $fieldName): bool {
551

552
        /** @psalm-suppress UndefinedMethod */
553
        return $this->getModel()->isRequiredField($fieldName);
8✔
554
    }
555

556
    /**
557
     * @psalm-suppress PossiblyUnusedMethod
558
     */
559
    public function getFieldLength(string $fieldName): ?int {
560

561
        /** @psalm-suppress UndefinedMethod */
562
        return $this->getModel()->getFieldLength($fieldName);
8✔
563
    }
564

565
    /**
566
     * @psalm-suppress PossiblyUnusedMethod
567
     */
568
    public function getFieldDefaultValue(string $fieldName): mixed {
569

570
        /** @psalm-suppress UndefinedMethod */
571
        return $this->getModel()->getFieldDefaultValue($fieldName);
8✔
572
    }
573

574
    /**
575
     * @psalm-suppress PossiblyUnusedMethod
576
     */
577
    public function getFieldMetadata(string $fieldName): array {
578

579
        /** @psalm-suppress UndefinedMethod */
580
        return $this->getModel()->getFieldMetadata($fieldName);
8✔
581
    }
582
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc