Completed
Pull Request — master (#2)
by Mathieu
05:06 queued 01:16
created

DBObject::__wakeup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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