Passed
Push — develop ( 684153...55880a )
by Mathieu
01:35
created

DBObject::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

527
            /** @scrutinizer ignore-call */ 
528
            $this->dbLink = Suricate::Database();
Loading history...
528
            if ($this->getDBConfig() !== '') {
529
                $this->dbLink->setConfig($this->getDBConfig());
530
            }
531
        }
532 9
    }
533
534 1
    public function validate()
535
    {
536 1
        return true;
537
    }
538
539
    public function getValidatorMessages()
540
    {
541
        return $this->validatorMessages;
542
    }
543
}
544