Passed
Push — develop ( 4e6958...d63a18 )
by Mathieu
01:41
created

DBObject::tableIndex()   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 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 13
    public function getTableName()
155
    {
156 13
        return $this->tableName;
157
    }
158
159 1
    public static function tableName()
160
    {
161 1
        return with(new static)->getTableName();
162
    }
163
164 16
    public function getTableIndex()
165
    {
166 16
        return $this->tableIndex;
167
    }
168
169 1
    public static function tableIndex()
170
    {
171 1
        return with(new static)->getTableIndex();
172
    }
173
174 1
    public function getDBConfig()
175
    {
176 1
        return $this->DBConfig;
177
    }
178
179
    /**
180
     * __sleep magic method, permits an inherited DBObject class to be serialized
181
     * @return Array of properties to serialize
182
     */
183
    public function __sleep()
184
    {
185
        $discardedProps = ['dbLink', 'relations'];
186
        $reflection     = new \ReflectionClass($this);
187
        $props          = $reflection->getProperties();
188
        $result         = [];
189
        foreach ($props as $currentProperty) {
190
            $result[] = $currentProperty->name;
191
        }
192
        
193
        return array_diff($result, $discardedProps);
194
    }
195
196 1
    public function __wakeup()
197
    {
198 1
        $this->dbLink = false;
199 1
        $this->setRelations();
200 1
    }
201
    
202
    /**
203
     * @param string $name
204
     */
205 14
    private function getDBVariable($name)
206
    {
207 14
        if (isset($this->dbValues[$name])) {
208 14
            return $this->dbValues[$name];
209
        }
210
211 5
        return null;
212
    }
213
214
    /**
215
     * Check if variable is from DB
216
     * @param  string  $name variable name
217
     * @return boolean
218
     */
219 21
    public function isDBVariable($name)
220
    {
221 21
        return in_array($name, $this->dbVariables);
222
    }
223
224 12
    private function resetLoadedVariables()
225
    {
226 12
        $this->loadedProtectedVariables = [];
227 12
        $this->loadedRelations          = [];
228 12
        $this->loaded                   = false;
229
230 12
        return $this;
231
    }
232
233
    /**
234
     * Check if requested property exists
235
     *
236
     * Check in following order:
237
     * <ul>
238
     *     <li>$dbVariables</li>
239
     *     <li>$protectedVariables</li>
240
     *     <li>$relations</li>
241
     *     <li>legacy property</li>
242
     * </ul>
243
     * @param  string $property Property name
244
     * @return boolean           true if exists
245
     */
246 8
    public function propertyExists($property)
247
    {
248 8
        return $this->isDBVariable($property)
249 2
            || $this->isProtectedVariable($property)
250 2
            || $this->isRelation($property)
251 8
            || property_exists($this, $property);
252
    }
253
254
    /**
255
     * Load ORM from Database
256
     * @param  mixed $id SQL Table Unique id
257
     * @return mixed     Loaded object or false on failure
258
     */
259 11
    public function load($id)
260
    {
261 11
        $this->connectDB();
262 11
        $this->resetLoadedVariables();
263
264 11
        $query  = "SELECT *";
265 11
        $query .= " FROM `" . $this->getTableName() ."`";
266 11
        $query .= " WHERE";
267 11
        $query .= "     `" . $this->getTableIndex() . "` =  :id";
268
        
269 11
        $params         = [];
270 11
        $params['id']   = $id;
271
272 11
        return $this->loadFromSql($query, $params);
273
    }
274
275 10
    public function isLoaded()
276
    {
277 10
        return $this->loaded;
278
    }
279
280 2
    public function loadOrFail($index)
281
    {
282 2
        $this->load($index);
283 2
        if ($this->{$this->getTableIndex()} != $index) {
284 2
            throw (new Exception\ModelNotFoundException)->setModel(get_called_class());
285
        }
286
287 2
        return $this;
288
    }
289
290
    public static function loadOrCreate($arg)
291
    {
292
        $obj = static::loadOrInstanciate($arg);
293
        $obj->save();
294
295
        return $obj;
296
    }
297
298
    /**
299
     * Load existing object by passing properties or instanciate if
300
     *
301
     * @param mixed $arg
302
     * @return DBObject
303
     */
304 1
    public static function loadOrInstanciate($arg)
305
    {
306 1
        $calledClass = get_called_class();
307 1
        $obj = new $calledClass;
308
        
309
        // got only one parameter ? consider as table index value (id)
310 1
        if (!is_array($arg)) {
311 1
            $arg = [$obj->getTableIndex() => $arg];
312
        }
313
        
314
315 1
        $sql = "SELECT *";
316 1
        $sql .= " FROM `" . $obj->getTableName() . "`";
317 1
        $sql .= " WHERE ";
318
319 1
        $sqlArray   = [];
320 1
        $params     = [];
321 1
        $offset = 0;
322 1
        foreach ($arg as $key => $val) {
323 1
            if (is_null($val)) {
324
                $sqlArray[] = '`' . $key . '` IS :arg' . $offset;
325
            } else {
326 1
                $sqlArray[] = '`' . $key . '`=:arg' . $offset;
327
            }
328 1
            $params['arg' .$offset] = $val;
329 1
            $offset++;
330
        }
331 1
        $sql .= implode(' AND ', $sqlArray);
332
333 1
        if (!$obj->loadFromSql($sql, $params)) {
334 1
            foreach ($arg as $property => $value) {
335 1
                $obj->$property = $value;
336
            }
337
        }
338
339 1
        return $obj;
340
    }
341
    
342
    /**
343
     * @param string $sql
344
     */
345 12
    public function loadFromSql(string $sql, $sqlParams = [])
346
    {
347 12
        $this->connectDB();
348 12
        $this->resetLoadedVariables();
349
        
350 12
        $results = $this->dbLink->query($sql, $sqlParams)->fetch();
351
352 12
        if ($results !== false) {
353 12
            foreach ($results as $key => $value) {
354 12
                $this->$key = $value;
355
            }
356 12
            $this->loaded = true;
357 12
            return $this;
358
        }
359
360 5
        return false;
361
    }
362
363
    /**
364
     * Construct an DBObject from an array
365
     * @param  array $data  associative array
366
     * @return DBObject       Built DBObject
367
     */
368 6
    public static function instanciate(array $data = [])
369
    {
370 6
        $calledClass    = get_called_class();
371 6
        $orm            = new $calledClass;
372
373 6
        return $orm->hydrate($data);
374
    }
375
376 7
    public function hydrate(array $data = [])
377
    {
378 7
        foreach ($data as $key => $val) {
379 7
            if ($this->propertyExists($key)) {
380 7
                $this->$key = $val;
381
            }
382
        }
383
384 7
        return $this;
385
    }
386
387 1
    public static function create(array $data = [])
388
    {
389 1
        $obj = static::instanciate($data);
390 1
        $obj->save();
391
392 1
        return $obj;
393
    }
394
    
395
    /**
396
     * Delete record from SQL Table
397
     *
398
     * Delete record link to current object, according SQL Table unique id
399
     * @return null
400
     */
401 1
    public function delete()
402
    {
403 1
        $this->connectDB();
404
405 1
        if ($this->getTableIndex() !== '') {
406 1
            $query  = "DELETE FROM `" . $this->getTableName() . "`";
407 1
            $query .= " WHERE `" . $this->getTableIndex() . "` = :id";
408
409 1
            $queryParams = [];
410 1
            $queryParams['id'] = $this->{$this->getTableIndex()};
411
            
412 1
            $this->dbLink->query($query, $queryParams);
413
        }
414 1
    }
415
    
416
    /**
417
     * Save current object into db
418
     *
419
     * Call INSERT or UPDATE if unique index is set
420
     * @param  boolean $forceInsert true to force insert instead of update
421
     * @return null
422
     */
423 4
    public function save($forceInsert = false)
424
    {
425 4
        if (count($this->dbValues)) {
426 4
            $this->connectDB();
427
428 4
            if ($this->isLoaded() && !$forceInsert) {
429 1
                $this->update();
430 1
                return;
431
            }
432
433 4
            $this->insert();
434 4
            return;
435
        }
436
437
        throw new \RuntimeException("Object " . get_called_class() . " has no properties to save");
438
    }
439
440
    /**
441
     * UPDATE current object into database
442
     * @return null
443
     */
444 1
    private function update()
445
    {
446 1
        $this->connectDB();
447
448 1
        $sqlParams = [];
449
450 1
        $sql  = 'UPDATE `' . $this->getTableName() . '`';
451 1
        $sql .= ' SET ';
452
        
453
454 1
        foreach ($this->dbValues as $key => $val) {
455 1
            if (!in_array($key, $this->readOnlyVariables)) {
456 1
                $sql .= ' `' . $key . '`=:' . $key .', ';
457 1
                $sqlParams[$key] = $val;
458
            }
459
        }
460 1
        $sql  = substr($sql, 0, -2);
461 1
        $sql .= " WHERE `" . $this->getTableIndex() . "` = :SuricateTableIndex";
462
463 1
        $sqlParams[':SuricateTableIndex'] = $this->{$this->getTableIndex()};
464
465 1
        $this->dbLink->query($sql, $sqlParams);
466 1
    }
467
468
    /**
469
     * INSERT current object into database
470
     * @access  private
471
     * @return null
472
     */
473 4
    private function insert()
474
    {
475 4
        $this->connectDB();
476
        
477 4
        $variables = array_diff($this->dbVariables, $this->readOnlyVariables);
478
479 4
        $sql  = 'INSERT INTO `' . $this->getTableName() . '`';
480 4
        $sql .= '(`';
481 4
        $sql .= implode('`, `', $variables);
482 4
        $sql .= '`)';
483 4
        $sql .= ' VALUES (:';
484 4
        $sql .= implode(', :', $variables);
485 4
        $sql .= ')';
486
487 4
        $sqlParams = [];
488 4
        foreach ($variables as $field) {
489 4
            $sqlParams[':' . $field] = $this->$field;
490
        }
491
492 4
        $this->dbLink->query($sql, $sqlParams);
493 4
        $this->loaded = true;
494 4
        $this->{$this->getTableIndex()} = $this->dbLink->lastInsertId();
495 4
    }
496
    
497 12
    protected function connectDB()
498
    {
499 12
        if (!$this->dbLink) {
500
            $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

500
            /** @scrutinizer ignore-call */ 
501
            $this->dbLink = Suricate::Database();
Loading history...
501
            if ($this->getDBConfig() !== '') {
502
                $this->dbLink->setConfig($this->getDBConfig());
503
            }
504
        }
505 12
    }
506
507 1
    public function validate()
508
    {
509 1
        return true;
510
    }
511
512
    public function getValidatorMessages()
513
    {
514
        return $this->validatorMessages;
515
    }
516
}
517