Completed
Push — master ( a75749...e45ba3 )
by Mathieu
01:40
created

DBObject::__get()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 14
rs 8.8571
c 2
b 0
f 0
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 $dbLink                       = false;
60
61
    protected $validatorMessages            = [];
62
    
63
64
    public function __construct()
65
    {
66
        $this->setRelations();
67
    }
68
    /**
69
     * Magic getter
70
     *
71
     * Try to get object property according this order :
72
     * <ul>
73
     *     <li>$dbVariable</li>
74
     *     <li>$protectedVariable (triggger call to accessToProtectedVariable()
75
     *         if not already loaded)</li>
76
     * </ul>
77
     *
78
     * @param  string $name     Property name
79
     * @return Mixed            Property value
80
     */
81
    public function __get($name)
82
    {
83
        if ($this->isDBVariable($name)) {
84
            return $this->getDBVariable($name);
85
        } elseif ($this->isProtectedVariable($name)) {
86
            return $this->getProtectedVariable($name);
87
        } elseif ($this->isRelation($name)) {
88
            return $this->getRelation($name);
89
        } elseif (!empty($this->$name)) {
90
            return $this->$name;
91
        } else {
92
            throw new \InvalidArgumentException('Undefined property ' . $name);
93
        }
94
    }
95
96
        /**
97
     * Magic setter
98
     *
99
     * Set a property to defined value
100
     * Assignment in this order :
101
     * - $dbVariable
102
     * - $protectedVariable
103
     *  </ul>
104
     * @param string $name  variable name
105
     * @param mixed $value variable value
106
     */
107
    public function __set($name, $value)
108
    {
109
        if ($this->isDBVariable($name)) {
110
            $this->dbValues[$name] = $value;
111
        } elseif ($this->isProtectedVariable($name)) {
112
            $this->protectedValues[$name] = $value;
113
        } elseif ($this->isRelation($name)) {
114
            $this->relationValues[$name] = $value;
115
        } else {
116
            $this->$name = $value;
117
        }
118
    }
119
120
    public function __isset($name)
121
    {
122
        if ($this->isDBVariable($name)) {
123
            return isset($this->dbValues[$name]);
124
        } elseif ($this->isProtectedVariable($name)) {
125
            // Load only one time protected variable automatically
126
            if (!$this->isProtectedVariableLoaded($name)) {
127
                $protectedAccessResult = $this->accessToProtectedVariable($name);
128
129
                if ($protectedAccessResult) {
130
                    $this->markProtectedVariableAsLoaded($name);
131
                }
132
            }
133
            return isset($this->protectedValues[$name]);
134
        } elseif ($this->isRelation($name)) {
135
            if (!$this->isRelationLoaded($name)) {
136
                $relationResult = $this->loadRelation($name);
137
138
                if ($relationResult) {
139
                    $this->markRelationAsLoaded($name);
140
                }
141
            }
142
            return isset($this->relationValues[$name]);
143
        } else {
144
            return false;
145
        }
146
    }
147
148
    /**
149
     * __sleep magic method, permits an inherited DBObject class to be serialized
150
     * @return Array of properties to serialize
151
     */
152
    public function __sleep()
153
    {
154
        $this->dbLink   = false;
155
        $this->relations= [];
156
        $reflection     = new \ReflectionClass($this);
157
        $props          = $reflection->getProperties();
158
        $result         = [];
159
        foreach ($props as $currentProperty) {
160
            $result[] = $currentProperty->name;
161
        }
162
163
        return $result;
164
    }
165
166
    public function __wakeup()
167
    {
168
        $this->setRelations();
169
    }
170
    
171
    private function getDBVariable($name)
172
    {
173
        if (isset($this->dbValues[$name])) {
174
            return $this->dbValues[$name];
175
        }
176
177
        return null;
178
    }
179
180
    /**
181
     * Check if variable is from DB
182
     * @param  string  $name variable name
183
     * @return boolean
184
     */
185
    public function isDBVariable($name)
186
    {
187
        return in_array($name, $this->dbVariables);
188
    }
189
190
    private function getProtectedVariable($name)
191
    {
192
        // Variable exists, and is already loaded
193
        if (isset($this->protectedValues[$name]) && $this->isProtectedVariableLoaded($name)) {
194
            return $this->protectedValues[$name];
195
        }
196
        // Variable has not been loaded
197
        if (!$this->isProtectedVariableLoaded($name)) {
198
            if ($this->accessToProtectedVariable($name)) {
199
                $this->markProtectedVariableAsLoaded($name);
200
            }
201
        }
202
203
        if (isset($this->protectedValues[$name])) {
204
            return $this->protectedValues[$name];
205
        }
206
207
        return null;
208
    }
209
210
    private function getRelation($name)
211
    {
212
        if (isset($this->relationValues[$name]) && $this->isRelationLoaded($name)) {
213
            return $this->relationValues[$name];
214
        }
215
216
        if (!$this->isRelationLoaded($name)) {
217
            if ($this->loadRelation($name)) {
218
                $this->markRelationAsLoaded($name);
219
            }
220
        }
221
222
        if (isset($this->relationValues[$name])) {
223
            return $this->relationValues[$name];
224
        }
225
226
        return null;
227
    }
228
229
    /**
230
     * Check if variable is predefined relation
231
     * @param  string  $name variable name
232
     * @return boolean
233
     */
234
    protected function isRelation($name)
235
    {
236
        return isset($this->relations[$name]);
237
    }
238
    /**
239
     * Define object relations
240
     *
241
     * @return object
242
     */
243
    protected function setRelations()
244
    {
245
        $this->relations = [];
246
247
        return $this;
248
    }
249
250
    /**
251
     * Mark a protected variable as loaded
252
     * @param  string $name varialbe name
253
     * @return void
254
     */
255
    public function markProtectedVariableAsLoaded($name)
256
    {
257
        if ($this->isProtectedVariable($name)) {
258
            $this->loadedProtectedVariables[$name] = true;
259
        }
260
    }
261
262
    /**
263
     * Mark a relation as loaded
264
     * @param  string $name varialbe name
265
     * @return void
266
     */
267
    protected function markRelationAsLoaded($name)
268
    {
269
        if ($this->isRelation($name)) {
270
            $this->loadedRelations[$name] = true;
271
        }
272
    }
273
     /**
274
     * Check if a relation already have been loaded
275
     * @param  string  $name Variable name
276
     * @return boolean
277
     */
278
    protected function isRelationLoaded($name)
279
    {
280
        return isset($this->loadedRelations[$name]);
281
    }
282
283
    protected function loadRelation($name)
284
    {
285
        if (isset($this->relations[$name])) {
286
            switch ($this->relations[$name]['type']) {
287
                case self::RELATION_ONE_ONE:
288
                    return $this->loadRelationOneOne($name);
289
                case self::RELATION_ONE_MANY:
290
                    return $this->loadRelationOneMany($name);
291
                case self::RELATION_MANY_MANY:
292
                    return $this->loadRelationManyMany($name);
293
            }
294
        }
295
296
        return false;
297
    }
298
299
    private function loadRelationOneOne($name)
300
    {
301
        $target = $this->relations[$name]['target'];
302
        $source = $this->relations[$name]['source'];
303
        $this->relationValues[$name] = new $target();
304
        $this->relationValues[$name]->load($this->$source);
305
        
306
        return true;
307
    }
308
309
    private function loadRelationOneMany($name)
310
    {
311
        $target         = $this->relations[$name]['target'];
312
        $parentId       = $this->{$this->relations[$name]['source']};
313
        $parentIdField  = isset($this->relations[$name]['target_field']) ? $this->relations[$name]['target_field'] : null;
314
        $validate       = dataGet($this->relations[$name], 'validate', null);
315
        
316
        $this->relationValues[$name] = $target::loadForParentId($parentId, $parentIdField, $validate);
317
318
        return true;
319
    }
320
321
    private function loadRelationManyMany($name)
322
    {
323
        $pivot      = $this->relations[$name]['pivot'];
324
        $sourceType = $this->relations[$name]['source_type'];
325
        $target     = dataGet($this->relations[$name], 'target');
326
        $validate   = dataGet($this->relations[$name], 'validate', null);
327
328
        $this->relationValues[$name] = $pivot::loadFor($sourceType, $this->{$this->relations[$name]['source']}, $target, $validate);
329
        
330
        return true;
331
    }
332
333
    private function resetLoadedVariables()
334
    {
335
        $this->loadedProtectedVariables = [];
336
        $this->loadedRelations          = [];
337
338
        return $this;
339
    }
340
341
    /**
342
     * Check if requested property exists
343
     *
344
     * Check in following order:
345
     * <ul>
346
     *     <li>$dbVariables</li>
347
     *     <li>$protectedVariables</li>
348
     *     <li>$relations</li>
349
     *     <li>legacy property</li>
350
     * </ul>
351
     * @param  string $property Property name
352
     * @return boolean           true if exists
353
     */
354
    public function propertyExists($property)
355
    {
356
        return $this->isDBVariable($property)
357
            || $this->isProtectedVariable($property)
358
            || $this->isRelation($property)
359
            || property_exists($this, $property);
360
    }
361
   
362
   /**
363
    * Check if variable is a protected variable
364
    * @param  string  $name variable name
365
    * @return boolean
366
    */
367
    public function isProtectedVariable($name)
368
    {
369
        return in_array($name, $this->protectedVariables);
370
    }
371
372
    
373
374
    /**
375
     * Check if a protected variable already have been loaded
376
     * @param  string  $name Variable name
377
     * @return boolean
378
     */
379
    protected function isProtectedVariableLoaded($name)
380
    {
381
        return isset($this->loadedProtectedVariables[$name]);
382
    }
383
384
    
385
    
386
    /**
387
     * Load ORM from Database
388
     * @param  mixed $id SQL Table Unique id
389
     * @return mixed     Loaded object or false on failure
390
     */
391
    public function load($id)
392
    {
393
        $this->connectDB();
394
        $this->resetLoadedVariables();
395
396
        if ($id != '') {
397
            $query  = "SELECT *";
398
            $query .= " FROM `" . static::TABLE_NAME ."`";
399
            $query .= " WHERE";
400
            $query .= "     `" . static::TABLE_INDEX . "` =  :id";
401
            
402
            $params         = [];
403
            $params['id']   = $id;
404
405
            return $this->loadFromSql($query, $params);
406
        }
407
        
408
        return $this;
409
    }
410
411
    public function isLoaded()
412
    {
413
        return $this->{static::TABLE_INDEX} !== null;
414
    }
415
416
    public function loadOrFail($id)
417
    {
418
        $this->load($id);
419
        if ($id == '' || $this->{static::TABLE_INDEX} != $id) {
420
            throw (new Exception\ModelNotFoundException)->setModel(get_called_class());
421
        } else {
422
            return $this;
423
        }
424
    }
425
426
    public static function loadOrCreate($arg)
427
    {
428
        $obj = static::loadOrInstanciate($arg);
429
        $obj->save();
430
431
        return $obj;
432
    }
433
434
    public static function loadOrInstanciate($arg)
435
    {
436
        if (!is_array($arg)) {
437
            $arg = [static::TABLE_INDEX => $arg];
438
        }
439
440
        $sql = "SELECT *";
441
        $sql .= " FROM " . static::TABLE_NAME;
442
        $sql .= " WHERE ";
443
444
        $sqlArray   = [];
445
        $params     = [];
446
        $i = 0;
447
        foreach ($arg as $key => $val) {
448
            if (is_null($val)) {
449
                $sqlArray[] = '`' . $key . '` IS :arg' . $i;
450
            } else {
451
                $sqlArray[] = '`' . $key . '`=:arg' . $i;    
452
            }
453
            $params['arg' .$i] = $val;
454
            $i++;
455
        }
456
        $sql .= implode(' AND ', $sqlArray);
457
458
459
460
        $calledClass = get_called_class();
461
        $obj = new $calledClass;
462
        if (!$obj->loadFromSql($sql, $params)) {
463
            foreach($arg as $property => $value) {
464
                $obj->$property = $value;
465
            }
466
        }
467
468
        return $obj;
469
    }
470
    
471
    public function loadFromSql($sql, $sql_params = [])
472
    {
473
        $this->connectDB();
474
        $this->resetLoadedVariables();
475
        
476
        $results = $this->dbLink->query($sql, $sql_params)->fetch();
477
478
        if ($results !== false) {
479
            foreach ($results as $key => $value) {
480
                $this->$key = $value;
481
            }
482
483
            return $this;
484
        }
485
486
        return false;
487
    }
488
489
    /**
490
     * Construct an DBObject from an array
491
     * @param  array $data  associative array
492
     * @return DBObject       Built DBObject
493
     */
494
    public static function instanciate($data = [])
495
    {
496
        $calledClass    = get_called_class();
497
        $orm            = new $calledClass;
498
499
        foreach ($data as $key => $val) {
500
            if ($orm->propertyExists($key)) {
501
                $orm->$key = $val;
502
            }
503
        }
504
        
505
        return $orm;
506
    }
507
508
    public static function create($data = []
509
    {
510
        $obj = static::instanciate($data);
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ';'
Loading history...
511
        $obj->save();
512
513
        return $obj;
514
    }
515
    
516
    /**
517
     * Delete record from SQL Table
518
     *
519
     * Delete record link to current object, according SQL Table unique id
520
     * @return null
521
     */
522
    public function delete()
523
    {
524
        $this->connectDB();
525
526
        if (static::TABLE_INDEX != '') {
527
            $query  = "DELETE FROM `" . static::TABLE_NAME . "`";
528
            $query .= " WHERE `" . static::TABLE_INDEX . "` = :id";
529
530
            $queryParams = [];
531
            $queryParams['id'] = $this->{static::TABLE_INDEX};
532
            
533
            $this->dbLink->query($query, $queryParams);
534
        }
535
    }
536
    
537
    /**
538
     * Save current object into db
539
     *
540
     * Call INSERT or UPDATE if unique index is set
541
     * @param  boolean $forceInsert true to force insert instead of update
542
     * @return null
543
     */
544
    public function save($forceInsert = false)
545
    {
546
        if (count($this->dbValues)) {
547
            $this->connectDB();
548
549
            if ($this->{static::TABLE_INDEX} != '' && !$forceInsert) {
550
                $this->update();
551
                $insert = false;
552
            } else {
553
                $this->insert();
554
                $insert = true;
555
            }
556
557
            // Checking protected variables
558
            foreach ($this->protectedVariables as $variable) {
559
                // only if current protected_var is set
560
                if (isset($this->protectedValues[$variable]) && $this->isProtectedVariableLoaded($variable)) {
561
                    if ($this->protectedValues[$variable] instanceof Interfaces\ICollection) {
562
                        if ($insert) {
563
                            $this->protectedValues[$variable]->setParentIdForAll($this->id);
564
                        }
565
                        $this->protectedValues[$variable]->save();
566
                    }
567
                }
568
            }
569
        } else {
570
            throw new \RuntimeException("Object " . get_called_class() . " has no properties to save");
571
        }
572
    }
573
574
    /**
575
     * UPDATE current object into database
576
     * @return null
577
     */
578
    private function update()
579
    {
580
        $this->connectDB();
581
582
        $sqlParams = [];
583
584
        $sql  = 'UPDATE `' . static::TABLE_NAME . '`';
585
        $sql .= ' SET ';
586
        
587
588
        foreach ($this->dbValues as $key => $val) {
589
            if (!in_array($key, $this->readOnlyVariables)) {
590
                $sql .= ' `' . $key . '`=:' . $key .', ';
591
                $sqlParams[$key] = $val;
592
            }
593
        }
594
        $sql  = substr($sql, 0, -2);
595
        $sql .= " WHERE `" . static::TABLE_INDEX . "` = :SuricateTableIndex";
596
597
        $sqlParams[':SuricateTableIndex'] = $this->{static::TABLE_INDEX};
598
599
        $this->dbLink->query($sql, $sqlParams);
600
    }
601
602
    /**
603
     * INSERT current object into database
604
     * @access  private
605
     * @return null
606
     */
607
    private function insert()
608
    {
609
        $this->connectDB();
610
        
611
        $variables = array_diff($this->dbVariables, $this->readOnlyVariables);
612
613
        $sql  = 'INSERT INTO `' . static::TABLE_NAME . '`';
614
        $sql .= '(`';
615
        $sql .= implode('`, `', $variables);
616
        $sql .= '`)';
617
        $sql .= ' VALUES (:';
618
        $sql .= implode(', :', $variables);
619
        $sql .= ')';
620
621
        $sqlParams = [];
622
        foreach ($variables as $field) {
623
            $sqlParams[':' . $field] = $this->$field;
624
        }
625
        
626
        $this->dbLink->query($sql, $sqlParams);
627
628
        $this->{static::TABLE_INDEX} = $this->dbLink->lastInsertId();
629
    }
630
    
631
    protected function connectDB()
632
    {
633
        if (!$this->dbLink) {
634
            $this->dbLink = Suricate::Database();
635
            if (static::DB_CONFIG != '') {
636
                $this->dbLink->setConfig(static::DB_CONFIG);
637
            }
638
        }
639
    }
640
    
641
    
642
    protected function accessToProtectedVariable($name)
643
    {
644
        return false;
645
    }
646
647
    public function validate()
648
    {
649
        return true;
650
    }
651
652
    public function getValidatorMessages()
653
    {
654
        return $this->validatorMessages;
655
    }
656
}
657