Passed
Push — develop ( 3cbb1a...ed14bd )
by Mathieu
01:40
created

DBObject   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 727
Duplicated Lines 0 %

Test Coverage

Coverage 13.08%

Importance

Changes 0
Metric Value
wmc 114
eloc 271
c 0
b 0
f 0
dl 0
loc 727
rs 2
ccs 39
cts 298
cp 0.1308

42 Methods

Rating   Name   Duplication   Size   Complexity  
A accessToProtectedVariable() 0 3 1
B loadOrInstanciate() 0 35 6
A isProtectedVariableLoaded() 0 3 1
A loadRelationOneOne() 0 8 1
A load() 0 18 2
A getValidatorMessages() 0 3 1
A setExportedVariables() 0 13 3
B save() 0 27 9
A __wakeup() 0 4 1
A markProtectedVariableAsLoaded() 0 4 2
A __sleep() 0 11 2
A loadRelationOneMany() 0 10 2
A loadOrCreate() 0 6 1
A delete() 0 12 2
A connectDB() 0 6 3
A validate() 0 3 1
A loadRelation() 0 14 5
A toJson() 0 3 1
A isLoaded() 0 3 1
A create() 0 6 1
A getRelation() 0 17 6
A update() 0 22 3
A insert() 0 22 2
A isRelationLoaded() 0 3 1
A loadFromSql() 0 16 3
A getProtectedVariable() 0 18 6
A instanciate() 0 12 3
A loadRelationManyMany() 0 10 1
A loadOrFail() 0 7 3
A markRelationAsLoaded() 0 4 2
A resetLoadedVariables() 0 6 1
B toArray() 0 37 9
A getDBVariable() 0 7 2
A isDBVariable() 0 3 1
A isRelation() 0 3 1
A setRelations() 0 5 1
A isProtectedVariable() 0 3 1
B __isset() 0 26 8
A __get() 0 13 5
A __construct() 0 3 1
A __set() 0 10 4
A propertyExists() 0 6 4

How to fix   Complexity   

Complex Class

Complex classes like DBObject 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 DBObject, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Suricate;
3
4
/**
5
 * DBObject, Pseudo ORM Class
6
 *
7
 * Two types of variables are available :
8
 * - $dbVariables, an array of fields contained in linked SQL table
9
 * - $protectedVariables, an array of variables not stored in SQL
10
 *     that can be triggered on access
11
 *
12
 * @package Suricate
13
 * @author  Mathieu LESNIAK <[email protected]>
14
 */
15
16
class DBObject implements Interfaces\IDBObject
17
{
18
    /*
19
    * @const TABLE_NAME : Linked SQL Table
20
    */
21
    const TABLE_NAME    = '';
22
23
    /*
24
    * @const TABLE_INDEX : Unique Id of the SQL Table
25
    */
26
    const TABLE_INDEX   = '';
27
28
    /**
29
     * @const DB_CONFIG : Database configuration identifier
30
     */
31
    const DB_CONFIG     = '';
32
33
    /**
34
     * @const RELATION_ONE_ONE : Relation one to one
35
     */
36
    const RELATION_ONE_ONE      = 1;
37
    /**
38
     * @const RELATION_ONE_MANY : Relation one to many
39
     */
40
    const RELATION_ONE_MANY     = 2;
41
    /**
42
     * @const RELATION_MANY_MANY : Relation many to many
43
     */
44
    const RELATION_MANY_MANY    = 3;
45
46
    protected $dbVariables                  = [];
47
    protected $dbValues                     = [];
48
    
49
    protected $protectedVariables           = [];
50
    protected $protectedValues              = [];
51
    protected $loadedProtectedVariables     = [];
52
53
    protected $readOnlyVariables            = [];
54
55
    protected $relations                    = [];
56
    protected $relationValues               = [];
57
    protected $loadedRelations              = [];
58
59
    protected $exportedVariables            = [];
60
61
    protected $dbLink                       = false;
62
63
    protected $validatorMessages            = [];
64
    
65
66 3
    public function __construct()
67
    {
68 3
        $this->setRelations();
69 3
    }
70
    /**
71
     * Magic getter
72
     *
73
     * Try to get object property according this order :
74
     * <ul>
75
     *     <li>$dbVariable</li>
76
     *     <li>$protectedVariable (triggger call to accessToProtectedVariable()
77
     *         if not already loaded)</li>
78
     * </ul>
79
     *
80
     * @param  string $name     Property name
81
     * @return Mixed            Property value
82
     */
83 2
    public function __get($name)
84
    {
85 2
        if ($this->isDBVariable($name)) {
86 1
            return $this->getDBVariable($name);
87 1
        } elseif ($this->isProtectedVariable($name)) {
88
            return $this->getProtectedVariable($name);
89 1
        } elseif ($this->isRelation($name)) {
90
            return $this->getRelation($name);
91 1
        } elseif (!empty($this->$name)) {
92
            return $this->$name;
93
        }
94
95 1
        throw new \InvalidArgumentException('Undefined property ' . $name);
96
    }
97
98
        /**
99
     * Magic setter
100
     *
101
     * Set a property to defined value
102
     * Assignment in this order :
103
     * - $dbVariable
104
     * - $protectedVariable
105
     *  </ul>
106
     * @param string $name  variable name
107
     * @param mixed $value variable value
108
     */
109 1
    public function __set($name, $value)
110
    {
111 1
        if ($this->isDBVariable($name)) {
112
            $this->dbValues[$name] = $value;
113 1
        } elseif ($this->isProtectedVariable($name)) {
114
            $this->protectedValues[$name] = $value;
115 1
        } elseif ($this->isRelation($name)) {
116
            $this->relationValues[$name] = $value;
117
        } else {
118 1
            $this->$name = $value;
119
        }
120 1
    }
121
122 1
    public function __isset($name)
123
    {
124 1
        if ($this->isDBVariable($name)) {
125
            return isset($this->dbValues[$name]);
126 1
        } elseif ($this->isProtectedVariable($name)) {
127
            // Load only one time protected variable automatically
128
            if (!$this->isProtectedVariableLoaded($name)) {
129
                $protectedAccessResult = $this->accessToProtectedVariable($name);
130
131
                if ($protectedAccessResult) {
0 ignored issues
show
introduced by
The condition $protectedAccessResult is always false.
Loading history...
132
                    $this->markProtectedVariableAsLoaded($name);
133
                }
134
            }
135
            return isset($this->protectedValues[$name]);
136 1
        } elseif ($this->isRelation($name)) {
137
            if (!$this->isRelationLoaded($name)) {
138
                $relationResult = $this->loadRelation($name);
139
140
                if ($relationResult) {
141
                    $this->markRelationAsLoaded($name);
142
                }
143
            }
144
            return isset($this->relationValues[$name]);
145
        }
146
147 1
        return false;
148
    }
149
150
    /**
151
     * __sleep magic method, permits an inherited DBObject class to be serialized
152
     * @return Array of properties to serialize
153
     */
154
    public function __sleep()
155
    {
156
        $discardedProps = ['dbLink', 'relations'];
157
        $reflection     = new \ReflectionClass($this);
158
        $props          = $reflection->getProperties();
159
        $result         = [];
160
        foreach ($props as $currentProperty) {
161
            $result[] = $currentProperty->name;
162
        }
163
        
164
        return array_diff($result, $discardedProps);
165
    }
166
167
    public function __wakeup()
168
    {
169
        $this->dbLink = false;
170
        $this->setRelations();
171
    }
172
    
173
    /**
174
     * @param string $name
175
     */
176 1
    private function getDBVariable($name)
177
    {
178 1
        if (isset($this->dbValues[$name])) {
179 1
            return $this->dbValues[$name];
180
        }
181
182 1
        return null;
183
    }
184
185
    /**
186
     * Check if variable is from DB
187
     * @param  string  $name variable name
188
     * @return boolean
189
     */
190 2
    public function isDBVariable($name)
191
    {
192 2
        return in_array($name, $this->dbVariables);
193
    }
194
195
    /**
196
     * @param string $name
197
     */
198
    private function getProtectedVariable($name)
199
    {
200
        // Variable exists, and is already loaded
201
        if (isset($this->protectedValues[$name]) && $this->isProtectedVariableLoaded($name)) {
202
            return $this->protectedValues[$name];
203
        }
204
        // Variable has not been loaded
205
        if (!$this->isProtectedVariableLoaded($name)) {
206
            if ($this->accessToProtectedVariable($name)) {
207
                $this->markProtectedVariableAsLoaded($name);
208
            }
209
        }
210
211
        if (isset($this->protectedValues[$name])) {
212
            return $this->protectedValues[$name];
213
        }
214
215
        return null;
216
    }
217
218
    /**
219
     * @param string $name
220
     */
221
    private function getRelation($name)
222
    {
223
        if (isset($this->relationValues[$name]) && $this->isRelationLoaded($name)) {
224
            return $this->relationValues[$name];
225
        }
226
227
        if (!$this->isRelationLoaded($name)) {
228
            if ($this->loadRelation($name)) {
229
                $this->markRelationAsLoaded($name);
230
            }
231
        }
232
233
        if (isset($this->relationValues[$name])) {
234
            return $this->relationValues[$name];
235
        }
236
237
        return null;
238
    }
239
240
    /**
241
     * Check if variable is predefined relation
242
     * @param  string  $name variable name
243
     * @return boolean
244
     */
245 2
    protected function isRelation($name)
246
    {
247 2
        return isset($this->relations[$name]);
248
    }
249
    /**
250
     * Define object relations
251
     *
252
     * @return DBObject
253
     */
254 2
    protected function setRelations()
255
    {
256 2
        $this->relations = [];
257
258 2
        return $this;
259
    }
260
261
    /**
262
     * Define object exported variables
263
     *
264
     * @return DBObject
265
     */
266
    protected function setExportedVariables()
267
    {
268
        if (count($this->exportedVariables)) {
269
            return $this;
270
        }
271
272
        $dbMappingExport = [];
273
        foreach ($this->dbVariables as $field) {
274
            $dbMappingExport[$field] = $field;
275
        }
276
        $this->exportedVariables = $dbMappingExport;
277
278
        return $this;
279
    }
280
281
    /**
282
     * Export DBObject to array
283
     *
284
     * @return array
285
     */
286
    public function toArray()
287
    {
288
        $this->setExportedVariables();
289
        $result = [];
290
        foreach ($this->exportedVariables as $sourceName => $destinationName) {
291
            $omitEmpty  = false;
292
            $castType   = null;
293
            if (strpos($destinationName, ',') !== false) {
294
                $splitted   = explode(',', $destinationName);
295
                array_map(function ($item) use (&$castType, &$omitEmpty) {
296
                    if ($item === 'omitempty') {
297
                        $omitEmpty = true;
298
                        return;
299
                    }
300
                    if (substr($item, 0, 5) === 'type:') {
301
                        $castType = substr($item, 5);
302
                    }
303
                }, $splitted);
304
305
                $destinationName = $splitted[0];
306
            }
307
308
            if ($destinationName === '-') {
309
                continue;
310
            }
311
312
            if ($omitEmpty && empty($this->$sourceName)) {
313
                continue;
314
            }
315
            $value = $this->$sourceName;
316
            if ($castType !== null) {
317
                settype($value, $castType);
318
            }
319
            $result[$destinationName] = $value;
320
        }
321
322
        return $result;
323
    }
324
325
    /**
326
     * Export DBObject to JSON format
327
     *
328
     * @return string
329
     */
330
    public function toJson()
331
    {
332
        return implode('', array_map('json_encode', $this->toArray()));
333
    }
334
335
    /**
336
     * Mark a protected variable as loaded
337
     * @param  string $name varialbe name
338
     * @return void
339
     */
340
    public function markProtectedVariableAsLoaded($name)
341
    {
342
        if ($this->isProtectedVariable($name)) {
343
            $this->loadedProtectedVariables[$name] = true;
344
        }
345
    }
346
347
    /**
348
     * Mark a relation as loaded
349
     * @param  string $name varialbe name
350
     * @return void
351
     */
352
    protected function markRelationAsLoaded($name)
353
    {
354
        if ($this->isRelation($name)) {
355
            $this->loadedRelations[$name] = true;
356
        }
357
    }
358
     /**
359
     * Check if a relation already have been loaded
360
     * @param  string  $name Variable name
361
     * @return boolean
362
     */
363
    protected function isRelationLoaded($name)
364
    {
365
        return isset($this->loadedRelations[$name]);
366
    }
367
368
    protected function loadRelation($name)
369
    {
370
        if (isset($this->relations[$name])) {
371
            switch ($this->relations[$name]['type']) {
372
                case self::RELATION_ONE_ONE:
373
                    return $this->loadRelationOneOne($name);
374
                case self::RELATION_ONE_MANY:
375
                    return $this->loadRelationOneMany($name);
376
                case self::RELATION_MANY_MANY:
377
                    return $this->loadRelationManyMany($name);
378
            }
379
        }
380
381
        return false;
382
    }
383
384
    private function loadRelationOneOne($name)
385
    {
386
        $target = $this->relations[$name]['target'];
387
        $source = $this->relations[$name]['source'];
388
        $this->relationValues[$name] = new $target();
389
        $this->relationValues[$name]->load($this->$source);
390
        
391
        return true;
392
    }
393
394
    private function loadRelationOneMany($name)
395
    {
396
        $target         = $this->relations[$name]['target'];
397
        $parentId       = $this->{$this->relations[$name]['source']};
398
        $parentIdField  = isset($this->relations[$name]['target_field']) ? $this->relations[$name]['target_field'] : null;
399
        $validate       = dataGet($this->relations[$name], 'validate', null);
400
        
401
        $this->relationValues[$name] = $target::loadForParentId($parentId, $parentIdField, $validate);
402
403
        return true;
404
    }
405
406
    private function loadRelationManyMany($name)
407
    {
408
        $pivot      = $this->relations[$name]['pivot'];
409
        $sourceType = $this->relations[$name]['source_type'];
410
        $target     = dataGet($this->relations[$name], 'target');
411
        $validate   = dataGet($this->relations[$name], 'validate', null);
412
413
        $this->relationValues[$name] = $pivot::loadFor($sourceType, $this->{$this->relations[$name]['source']}, $target, $validate);
414
        
415
        return true;
416
    }
417
418
    private function resetLoadedVariables()
419
    {
420
        $this->loadedProtectedVariables = [];
421
        $this->loadedRelations          = [];
422
423
        return $this;
424
    }
425
426
    /**
427
     * Check if requested property exists
428
     *
429
     * Check in following order:
430
     * <ul>
431
     *     <li>$dbVariables</li>
432
     *     <li>$protectedVariables</li>
433
     *     <li>$relations</li>
434
     *     <li>legacy property</li>
435
     * </ul>
436
     * @param  string $property Property name
437
     * @return boolean           true if exists
438
     */
439 1
    public function propertyExists($property)
440
    {
441 1
        return $this->isDBVariable($property)
442 1
            || $this->isProtectedVariable($property)
443 1
            || $this->isRelation($property)
444 1
            || property_exists($this, $property);
445
    }
446
   
447
   /**
448
    * Check if variable is a protected variable
449
    * @param  string  $name variable name
450
    * @return boolean
451
    */
452 2
    public function isProtectedVariable($name)
453
    {
454 2
        return in_array($name, $this->protectedVariables);
455
    }
456
457
    
458
459
    /**
460
     * Check if a protected variable already have been loaded
461
     * @param  string  $name Variable name
462
     * @return boolean
463
     */
464
    protected function isProtectedVariableLoaded($name)
465
    {
466
        return isset($this->loadedProtectedVariables[$name]);
467
    }
468
469
    
470
    
471
    /**
472
     * Load ORM from Database
473
     * @param  mixed $id SQL Table Unique id
474
     * @return mixed     Loaded object or false on failure
475
     */
476
    public function load($id)
477
    {
478
        $this->connectDB();
479
        $this->resetLoadedVariables();
480
481
        if ($id != '') {
482
            $query  = "SELECT *";
483
            $query .= " FROM `" . static::TABLE_NAME ."`";
484
            $query .= " WHERE";
485
            $query .= "     `" . static::TABLE_INDEX . "` =  :id";
486
            
487
            $params         = [];
488
            $params['id']   = $id;
489
490
            return $this->loadFromSql($query, $params);
491
        }
492
        
493
        return $this;
494
    }
495
496
    public function isLoaded()
497
    {
498
        return $this->{static::TABLE_INDEX} !== null;
499
    }
500
501
    public function loadOrFail($id)
502
    {
503
        $this->load($id);
504
        if ($id == '' || $this->{static::TABLE_INDEX} != $id) {
505
            throw (new Exception\ModelNotFoundException)->setModel(get_called_class());
506
        } else {
507
            return $this;
508
        }
509
    }
510
511
    public static function loadOrCreate($arg)
512
    {
513
        $obj = static::loadOrInstanciate($arg);
514
        $obj->save();
515
516
        return $obj;
517
    }
518
519
    public static function loadOrInstanciate($arg)
520
    {
521
        if (!is_array($arg)) {
522
            $arg = [static::TABLE_INDEX => $arg];
523
        }
524
525
        $sql = "SELECT *";
526
        $sql .= " FROM " . static::TABLE_NAME;
527
        $sql .= " WHERE ";
528
529
        $sqlArray   = [];
530
        $params     = [];
531
        $i = 0;
532
        foreach ($arg as $key => $val) {
533
            if (is_null($val)) {
534
                $sqlArray[] = '`' . $key . '` IS :arg' . $i;
535
            } else {
536
                $sqlArray[] = '`' . $key . '`=:arg' . $i;
537
            }
538
            $params['arg' .$i] = $val;
539
            $i++;
540
        }
541
        $sql .= implode(' AND ', $sqlArray);
542
543
544
545
        $calledClass = get_called_class();
546
        $obj = new $calledClass;
547
        if (!$obj->loadFromSql($sql, $params)) {
548
            foreach ($arg as $property => $value) {
549
                $obj->$property = $value;
550
            }
551
        }
552
553
        return $obj;
554
    }
555
    
556
    /**
557
     * @param string $sql
558
     */
559
    public function loadFromSql($sql, $sqlParams = [])
560
    {
561
        $this->connectDB();
562
        $this->resetLoadedVariables();
563
        
564
        $results = $this->dbLink->query($sql, $sqlParams)->fetch();
565
566
        if ($results !== false) {
567
            foreach ($results as $key => $value) {
568
                $this->$key = $value;
569
            }
570
571
            return $this;
572
        }
573
574
        return false;
575
    }
576
577
    /**
578
     * Construct an DBObject from an array
579
     * @param  array $data  associative array
580
     * @return DBObject       Built DBObject
581
     */
582
    public static function instanciate($data = [])
583
    {
584
        $calledClass    = get_called_class();
585
        $orm            = new $calledClass;
586
587
        foreach ($data as $key => $val) {
588
            if ($orm->propertyExists($key)) {
589
                $orm->$key = $val;
590
            }
591
        }
592
        
593
        return $orm;
594
    }
595
596
    public static function create($data = [])
597
    {
598
        $obj = static::instanciate($data);
599
        $obj->save();
600
601
        return $obj;
602
    }
603
    
604
    /**
605
     * Delete record from SQL Table
606
     *
607
     * Delete record link to current object, according SQL Table unique id
608
     * @return null
609
     */
610
    public function delete()
611
    {
612
        $this->connectDB();
613
614
        if (static::TABLE_INDEX != '') {
0 ignored issues
show
introduced by
The condition static::TABLE_INDEX != '' is always false.
Loading history...
615
            $query  = "DELETE FROM `" . static::TABLE_NAME . "`";
616
            $query .= " WHERE `" . static::TABLE_INDEX . "` = :id";
617
618
            $queryParams = [];
619
            $queryParams['id'] = $this->{static::TABLE_INDEX};
620
            
621
            $this->dbLink->query($query, $queryParams);
622
        }
623
    }
624
    
625
    /**
626
     * Save current object into db
627
     *
628
     * Call INSERT or UPDATE if unique index is set
629
     * @param  boolean $forceInsert true to force insert instead of update
630
     * @return null
631
     */
632
    public function save($forceInsert = false)
633
    {
634
        if (count($this->dbValues)) {
635
            $this->connectDB();
636
637
            if ($this->{static::TABLE_INDEX} != '' && !$forceInsert) {
638
                $this->update();
639
                $insert = false;
640
            } else {
641
                $this->insert();
642
                $insert = true;
643
            }
644
645
            // Checking protected variables
646
            foreach ($this->protectedVariables as $variable) {
647
                // only if current protected_var is set
648
                if (isset($this->protectedValues[$variable]) && $this->isProtectedVariableLoaded($variable)) {
649
                    if ($this->protectedValues[$variable] instanceof Interfaces\ICollection) {
650
                        if ($insert) {
651
                            $this->protectedValues[$variable]->setParentIdForAll($this->{static::TABLE_INDEX});
652
                        }
653
                        $this->protectedValues[$variable]->save();
654
                    }
655
                }
656
            }
657
        } else {
658
            throw new \RuntimeException("Object " . get_called_class() . " has no properties to save");
659
        }
660
    }
661
662
    /**
663
     * UPDATE current object into database
664
     * @return null
665
     */
666
    private function update()
667
    {
668
        $this->connectDB();
669
670
        $sqlParams = [];
671
672
        $sql  = 'UPDATE `' . static::TABLE_NAME . '`';
673
        $sql .= ' SET ';
674
        
675
676
        foreach ($this->dbValues as $key => $val) {
677
            if (!in_array($key, $this->readOnlyVariables)) {
678
                $sql .= ' `' . $key . '`=:' . $key .', ';
679
                $sqlParams[$key] = $val;
680
            }
681
        }
682
        $sql  = substr($sql, 0, -2);
683
        $sql .= " WHERE `" . static::TABLE_INDEX . "` = :SuricateTableIndex";
684
685
        $sqlParams[':SuricateTableIndex'] = $this->{static::TABLE_INDEX};
686
687
        $this->dbLink->query($sql, $sqlParams);
688
    }
689
690
    /**
691
     * INSERT current object into database
692
     * @access  private
693
     * @return null
694
     */
695
    private function insert()
696
    {
697
        $this->connectDB();
698
        
699
        $variables = array_diff($this->dbVariables, $this->readOnlyVariables);
700
701
        $sql  = 'INSERT INTO `' . static::TABLE_NAME . '`';
702
        $sql .= '(`';
703
        $sql .= implode('`, `', $variables);
704
        $sql .= '`)';
705
        $sql .= ' VALUES (:';
706
        $sql .= implode(', :', $variables);
707
        $sql .= ')';
708
709
        $sqlParams = [];
710
        foreach ($variables as $field) {
711
            $sqlParams[':' . $field] = $this->$field;
712
        }
713
        
714
        $this->dbLink->query($sql, $sqlParams);
715
716
        $this->{static::TABLE_INDEX} = $this->dbLink->lastInsertId();
717
    }
718
    
719
    protected function connectDB()
720
    {
721
        if (!$this->dbLink) {
722
            $this->dbLink = Suricate::Database();
0 ignored issues
show
Bug introduced by
The method Database() does not exist on Suricate\Suricate. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

722
            /** @scrutinizer ignore-call */ 
723
            $this->dbLink = Suricate::Database();
Loading history...
723
            if (static::DB_CONFIG != '') {
0 ignored issues
show
introduced by
The condition static::DB_CONFIG != '' is always false.
Loading history...
724
                $this->dbLink->setConfig(static::DB_CONFIG);
725
            }
726
        }
727
    }
728
    
729
    
730
    protected function accessToProtectedVariable($name)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed. ( Ignorable by Annotation )

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

730
    protected function accessToProtectedVariable(/** @scrutinizer ignore-unused */ $name)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
731
    {
732
        return false;
733
    }
734
735
    public function validate()
736
    {
737
        return true;
738
    }
739
740
    public function getValidatorMessages()
741
    {
742
        return $this->validatorMessages;
743
    }
744
}
745