Passed
Push — develop ( 95162b...7c0612 )
by Mathieu
01:39
created

DBObject::loadRelationOneMany()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

630
            /** @scrutinizer ignore-call */ 
631
            $this->dbLink = Suricate::Database();
Loading history...
631
            if ($this->getDBConfig() !== '') {
632
                $this->dbLink->setConfig($this->getDBConfig());
633
            }
634
        }
635 4
    }
636
    
637
    
638
    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

638
    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...
639
    {
640
        return false;
641
    }
642
643
    public function validate()
644
    {
645
        return true;
646
    }
647
648
    public function getValidatorMessages()
649
    {
650
        return $this->validatorMessages;
651
    }
652
}
653