Completed
Push — develop ( d5f7f5...0c4bef )
by Mathieu
01:41
created

DBObject::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

490
            /** @scrutinizer ignore-call */ 
491
            $this->dbLink = Suricate::Database();
Loading history...
491
            if ($this->getDBConfig() !== '') {
492
                $this->dbLink->setConfig($this->getDBConfig());
493
            }
494
        }
495 11
    }
496
497 1
    public function validate()
498
    {
499 1
        return true;
500
    }
501
502
    public function getValidatorMessages()
503
    {
504
        return $this->validatorMessages;
505
    }
506
}
507