Issues (47)

lib/Model.php (14 issues)

1
<?php
2
3
namespace MocOrm\Model;
4
5
use MocOrm\Connection\ConnectionManager;
6
use MocOrm\Support\Log;
7
8
abstract class Model extends Query implements \JsonSerializable
9
{
10
    /**
11
     * @var Model $_instance Save current instance
12
     */
13
    private static $_instance;
14
15
    /**
16
     * @var \MocOrm\Connection\Connection $Connection Save connection instance
17
     */
18
    private $Connection;
19
20
    /**
21
     *  @var array|boolean $_data Save data on object
22
     */
23
    private $_data = [];
24
25
    /**
26
     *  @var array|boolean $_newData Set attributes for update
27
     */
28
    private $_newData = [];
29
30
    /**
31
     *  @var \Closure $triggerAfter Actived trigger after
32
     */
33
    private $triggerAfter = null;
34
35
    /**
36
     *  @var \Closure $triggerBefore Actived trigger after
37
     */
38
    private $triggerBefore = null;
39
40
    /**
41
     *  @var array $_current_custom_query_values Save the value to custom query
42
     */
43
    protected $_current_custom_query_values = [];
44
45
    private $Result;
46
47
    protected static $table_name;
48
    protected static $primary_key;
49
50
    /**
51
     * Model constructor.
52
     * set connection in var and set this instance in var for interator
53
     * @param array|null $array
54
     * @throws \Exception
55
     */
56
    public function __construct($array = null)
57
    {
58
        $methods = get_class_methods(get_called_class());
59
60
        try {
61
            $this->Connection = ConnectionManager::initialize()->current();
62
            self::$_instance = $this;
63
64
            $this->cleanNewData();
65
66
            if (in_array('onLoad', $methods)) {
67
                $this->/** @scrutinizer ignore-call */onLoad();
68
            }
69
70
            if (!is_null($array)) {
71
                if (!is_array($array)) throw new \InvalidArgumentException('Accept only array from object');
0 ignored issues
show
The condition is_array($array) is always true.
Loading history...
72
73
                $this->_data = $array;
74
                $this->_newData = $array;
75
            }
76
77
            $this->Result = new Result();
78
79
            if($this->Result && !$this->Result->getResults()) {
80
                $clone = clone $this;
81
                $this->Result->setResults($clone);
82
            }
83
        } catch (\Exception $e) {
84
            throw $e;
85
        }
86
    }
87
88
    /**
89
     * Using if call isset on attributes
90
     * @param $name
91
     * @return bool
92
     */
93
    public function __isset($name)
94
    {
95
        return key_exists($name, $this->_data);
0 ignored issues
show
It seems like $this->_data can also be of type boolean; however, parameter $search of key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

95
        return key_exists($name, /** @scrutinizer ignore-type */ $this->_data);
Loading history...
96
    }
97
98
    /**
99
     * Get value on array data
100
     * @param String $name
101
     * @return \MocOrm\Model\Error|mixed
102
     */
103
    public function __get($name)
104
    {
105
        if (strtolower($name) == 'errors') return Error::instance();
106
107
        if (!key_exists($name, $this->_data)) throw new \Exception("The attribute $name not found.");
0 ignored issues
show
It seems like $this->_data can also be of type boolean; however, parameter $search of key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
        if (!key_exists($name, /** @scrutinizer ignore-type */ $this->_data)) throw new \Exception("The attribute $name not found.");
Loading history...
108
109
        return $this->_data[$name];
110
    }
111
112
    /**
113
     * This set values in attribute data and newData
114
     * @param $name
115
     * @param $value
116
     */
117
    public function __set($name, $value)
118
    {
119
        $this->_newData[$name] = $value;
120
        $this->_data[$name] = $value;
121
    }
122
123
    /**
124
     * @return array|boolean Save for info in debug only attributes in _data
125
     */
126
    public function __debugInfo()
127
    {
128
        return $this->_data;
129
    }
130
131
    /**
132
     * @param $name
133
     */
134
    public function __unset($name)
135
    {
136
        unset($this->_data[$name]);
137
        unset($this->_newData[$name]);
138
    }
139
140
    public function __clone()
141
    {
142
        unset($this->_data[static::$primary_key]);
143
        $this->_newData = $this->_data;
144
    }
145
146
    /**
147
     * Insert the new data in database not static mode.
148
     * @param array $attributes Parameters, this is a mirror on database.
149
     * @return bool|Model False if not save in databse, and this instance if save.
150
     * @throws \Exception If the parameters isn't one array.
151
     */
152
    public function save()
153
    {
154
        try {
155
            $this->verifyConnection();
156
        } catch (\Exception $e) {
157
            throw $e;
158
        }
159
160
        $update = false;
161
162
        $key = static::$primary_key;
163
        $_tableName = static::$table_name;
164
165
        $repeat = substr(str_repeat(' ?, ', count($this->_newData)), 0, -2);
0 ignored issues
show
It seems like $this->_newData can also be of type boolean; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
        $repeat = substr(str_repeat(' ?, ', count(/** @scrutinizer ignore-type */ $this->_newData)), 0, -2);
Loading history...
166
167
        /**
168
         * Edit case have primary key value
169
         */
170
        if (!empty($this->_data[$key])) {
171
            $logger = [
172
              'function' => 'Update',
173
              'next' => $this->_data,
174
              'prev' => $this->Result->getResults()
175
            ];
176
177
            $this->saveLogger($logger);
178
            $this->Result->setResults(clone $this);
179
180
            $sql = 'UPDATE ' . $_tableName . ' SET ';
181
182
            if (count($this->_newData) <= 0) {
183
                Error::create('Don\'t have alter data.', 1, 'InvalidArgumentException');
184
                return false;
185
            }
186
187
            $sql .= implode(" = ?, ", array_keys($this->_newData)) . ' = ?';
0 ignored issues
show
It seems like $this->_newData can also be of type boolean; however, parameter $input of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
            $sql .= implode(" = ?, ", array_keys(/** @scrutinizer ignore-type */ $this->_newData)) . ' = ?';
Loading history...
188
            $sql .= " WHERE $key = ? ";
189
190
            $this->_newData[] = $this->{$key};
191
192
            $update = true;
193
        } else {
194
            $logger = [
195
                'function' => 'Save',
196
                'next' => $this->_data,
197
            ];
198
199
            $this->saveLogger($logger);
200
201
            /**
202
             * Insert case don't have primary key
203
             */
204
            $sql = 'INSERT INTO ' . $_tableName;
205
            $sql .= ' (' . implode(', ', array_keys($this->_data)) . ') ';
206
            $sql .= " VALUES ";
207
            $sql .= " ($repeat); ";
208
        }
209
210
        if (is_callable($this->triggerBefore)) ($this->triggerBefore)();
211
212
        $start = microtime(true);
213
        $insert = $this->Connection->getConnection()->prepare($sql);
214
        $this->burnError($insert);
215
216
        $this->_newData = array_map(function ($data) {
217
            if (is_bool($data) and $data === false) $data = 0;
218
219
            return $data;
220
        }, $this->_newData);
0 ignored issues
show
It seems like $this->_newData can also be of type boolean; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
        }, /** @scrutinizer ignore-type */ $this->_newData);
Loading history...
221
222
        $insert->execute(array_values($this->_newData));
223
        $end = microtime(true);
224
        $this->burnError($insert);
225
226
        $this->Connection->setPerformedQuery($insert->queryString, round(($end - $start), 5));
227
228
        if (is_callable($this->triggerAfter)) ($this->triggerAfter)();
229
230
        if ($update) {
231
            $this->burnError($insert);
232
233
            $this->cleanNewData();
234
            return true;
235
        }
236
237
        $this->_data[$key] = $this->Connection->getConnection()->lastInsertId();
238
239
        if ($insert->rowCount() == 0) {
240
            throw new \Exception($insert->errorInfo()[2], $insert->errorInfo()[1]);
241
        }
242
243
        return true;
244
    }
245
246
    private function saveLogger($params) {
247
        $class = get_called_class();
248
249
        if (
250
            $this->Connection->getAppLogger() and
251
            $class != 'MocOrm\Support\Log'
252
        ) {
253
254
            $log = (new Log)
255
                ->setNext(@$params['next'])
256
                ->setName($class)
257
                ->setFunction($params['function'])
258
                ->setPrev(@$params['prev']);
259
260
            $log->save();
261
        }
262
    }
263
264
265
    private function burnError($statment)
266
    {
267
        if (!is_null($statment->errorInfo()[1])) throw new \Exception($statment->errorInfo()[2], $statment->errorInfo()[1]);
268
    }
269
270
    /**
271
     * Delete the register
272
     * @return bool
273
     */
274
    public function delete()
275
    {
276
        try {
277
            $this->verifyConnection();
278
        } catch (\Exception $e) {
279
            throw new \Exception($e->getMessage());
280
        }
281
282
        if (!isset(static::$primary_key)) throw new \Exception('Primary key don\'t set');
283
284
        if (!is_numeric($this->{static::$primary_key})) throw new \Exception('Primary key value don\'t is valid');
285
286
        $sql = ' DELETE FROM ' . static::$table_name;
287
        $sql .= ' WHERE ' . static::$primary_key . ' = ? ';
288
289
        $instance = self::$_instance;
290
291
        if (is_callable($this->triggerBefore)) ($this->triggerBefore)();;
292
293
        $start = microtime(true);
294
295
        $insert = $instance->Connection->getConnection()->prepare($sql);
296
        $insert->execute([$this->{static::$primary_key}]);
297
298
        $end = microtime(true);
299
300
        $instance->Connection->setPerformedQuery($insert->queryString, round(($end - $start), 5));
301
302
        if (is_callable($this->triggerAfter)) ($this->triggerAfter)();
303
304
        if ($insert->rowCount() > 0) {
305
            $logger = [
306
                'function' => 'Delete',
307
                'prev' => $this->_data,
308
            ];
309
310
            $this->saveLogger($logger);
311
312
            return true;
313
        }
314
        else{ return false; };
315
    }
316
317
    /**
318
     * JsonSerializable Interface.
319
     */
320
    public function jsonSerialize()
321
    {
322
        return $this->_data;
323
    }
324
325
    /**
326
     * Get all data on database needed table name in Model
327
     * @throws \Exception Don't set table name in model.
328
     * @return Model|array|boolean all data in format Object
329
     */
330
    public static function all()
331
    {
332
        self::instance();
333
334
        if (!isset(static::$table_name)) throw new \Exception('Don\'t set table name in model.');
335
336
        $currentTable = static::$table_name;
337
338
        $instance = self::$_instance;
339
340
        try {
341
            self::$_instance->verifyConnection();
342
        } catch (\Exception $e) {
343
            throw new \Exception($e->getMessage());
344
        }
345
346
        $sql = "SELECT * FROM $currentTable ";
347
348
        return $instance->query($sql);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance->query($sql) also could return the type integer which is incompatible with the documented return type boolean|array|MocOrm\Model\Model.
Loading history...
349
    }
350
351
    /**
352
     * Execute one procedure
353
     * @param string $procedureName Name of the procedure
354
     * @param array $param Parameters needed in procedure
355
     * @return mixed Result on procedure
356
     * @throws \Exception Case procedureName don't is string or param not is array
357
     */
358
    public static function procedure($procedureName, $param = [])
359
    {
360
        self::instance();
361
362
        if (!is_string($procedureName)) throw new \Exception("Procedure name is invalid.");
0 ignored issues
show
The condition is_string($procedureName) is always true.
Loading history...
363
        if (!is_array($param)) throw new \Exception("Tipo de parâmetros inválidos.");
0 ignored issues
show
The condition is_array($param) is always true.
Loading history...
364
365
        $currentTable = static::$table_name;
366
367
        $instance = self::$_instance;
368
369
        try {
370
            self::$_instance->verifyConnection();
371
        } catch (\Exception $e) {
372
            Throw new \Exception($e->getMessage());
373
        }
374
375
        $repeat = substr(str_repeat(' ?, ', count($param)), 0, -2);
376
377
        $drivers = $instance->Connection->getDriver();
378
379
        switch ($drivers) {
380
            case 'mysql':
381
                $sql = "call $currentTable ($repeat)";
382
                break;
383
            case 'pgsql':
384
                $sql = "select $procedureName($repeat)";
385
                break;
386
            default:
387
                throw new \Exception('Don\'t exists implementation on this driver.');
388
                break;
389
        }
390
391
        $start = microtime(true);
392
        $consulta = $instance->Connection->getConnection()->prepare($sql);
393
        $consulta->execute($param);
394
        $objetos = $consulta->fetchAll(\PDO::FETCH_CLASS, get_class($instance));
395
        $end = microtime(true);
396
397
        $instance->Connection->setPerformedQuery($sql, round(($end - $start), 5));
398
399
        return $instance->_data = $objetos;
400
    }
401
402
    /**
403
     * Insert the new data in database static mode.
404
     * @param array $attributes Parameters, this is a mirror on database.
405
     * @return bool|Model False if not save in databse, and this instance if save.
406
     * @throws \Exception If the parameters isn't one array.
407
     */
408
    public static function create($attributes = [])
409
    {
410
        self::instance();
411
412
        $instance = self::$_instance;
413
414
        $instance->_data = $attributes;
415
        $instance->_newData = $attributes;
416
417
        return $instance->save();
418
    }
419
420
    /**
421
     * Find the data on primary key
422
     * @param Array|Integer $parameters Value on primary key
423
     * @return mixed Data or false
424
     * @throws \Exception if Parameter is invalid
425
     */
426
    public static function find($parameters = null)
427
    {
428
        self::instance();
429
430
        $instance = self::$_instance;
431
432
        try {
433
            $instance->verifyConnection();
434
        } catch (\Exception $e) {
435
            throw new \Exception($e->getMessage());
436
        }
437
438
        if (!is_array($parameters) and !is_numeric($parameters)) throw new \Exception('Invalid parameter type on model ' . get_called_class() . '.');
439
440
        $instance->_current_custom_query[] = 'SELECT * FROM ' . static::$table_name . ' ';
441
442
        switch ($parameters) {
443
            case is_numeric($parameters):
444
                if (!isset(static::$primary_key)) throw new \Exception("Invalid parameter type.");
445
446
                $instance->_current_custom_query_values[] = $parameters;
447
                $instance->_current_custom_query[] = ' WHERE ' . static::$primary_key . ' = ?';
448
449
                break;
450
            case is_array($parameters):
451
                break;
452
            default:
453
                throw new \Exception('Invalid parameter type.');
454
                break;
455
        }
456
457
        $done = $instance->done();
458
459
        if(count($done) > 0) {
460
            $done = $done[0];
461
462
            $clone = clone $done;
463
464
            $done->Result->setResults($clone);
465
466
            return $done;
467
        }else {
468
            return null;
469
        }
470
    }
471
472
    /**
473
     * Init the get data dynamic, the last method to use this is done() for execute
474
     * @param String $colunm This is all colunm from select
475
     * @return Model|boolean A model if success and false if don't have success
476
     * @throws \Exception If colunm isn't String
477
     */
478
    final public static function select($colunm = '*')
479
    {
480
        self::instance();
481
482
        try {
483
            self::$_instance->verifyConnection();
484
        } catch (\Exception $e) {
485
            throw new \Exception($e->getMessage());
486
        }
487
488
        if (!is_string($colunm)) throw new \Exception("Invalid parameter type.");
0 ignored issues
show
The condition is_string($colunm) is always true.
Loading history...
489
490
        self::$_instance->_current_custom_query[] = "SELECT $colunm FROM " . static::$table_name . ' ';
491
492
        if (!isset(static::$primary_key)) throw new \Exception("Invalid parameter type.");
493
494
        return self::$_instance;
495
    }
496
497
    /**
498
     * Get current connection name
499
     * @return string
500
     */
501
    protected function getCurrentConnectionName()
502
    {
503
        return $this->Connection->getCurrentConnectionString();
504
    }
505
506
    /**
507
     * Define the current connection on name
508
     * @param String $connectionName This is connection name
509
     * @return Model This from other implementations
510
     */
511
    protected function setConnection($connectionName)
512
    {
513
        try {
514
            $this->Connection->setConnection($connectionName);
0 ignored issues
show
The call to MocOrm\Connection\Connection::setConnection() has too many arguments starting with $connectionName. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

514
            $this->Connection->/** @scrutinizer ignore-call */ 
515
                               setConnection($connectionName);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
515
516
            return $this;
517
        } catch (\Exception $e) {
518
            throw new $e;
519
        }
520
    }
521
522
    /**
523
     * Execute the query
524
     * @param String $query The query for execute
525
     * @return Array|Model|boolean|integer $objects Results on query executed
526
     */
527
    protected function query($query, $param = [])
528
    {
529
        $this->_data = [];
530
531
        $select = trim($query);
532
        $select = strtolower($select);
533
534
        $match = preg_match('/^select|return|^with\srecursive/', $select);
535
536
        try {
537
            $this->verifyConnection();
538
        } catch (\Exception $e) {
539
            throw new \Exception($e->getMessage());
540
        }
541
542
        if (!is_array($param)) throw new \Exception('Tipo de parâmetro inválido.');
543
544
        $start = microtime(true);
545
546
        $consulta = $this->Connection->getConnection()->prepare($query);
547
        $this->burnError($consulta);
548
549
        $consulta->execute($param);
550
        $this->burnError($consulta);
551
552
        if (!$consulta) {
553
            $this->_data = false;
554
            return $this;
555
        }
556
557
        $end = microtime(true);
558
559
        if ($match) {
560
            $this->_data = $objetos = $consulta->fetchAll(\PDO::FETCH_CLASS, get_called_class());
561
        } else {
562
            $objetos = $consulta->rowCount();
563
        }
564
565
        $this->Connection->setPerformedQuery($query, round(($end - $start), 5));
566
567
        $this->burnError($consulta);
568
569
        return $objetos;
570
    }
571
572
    /**
573
     * Execute the query on static method
574
     * @param String $query The query for execute
575
     * @return array $objects Results on query executed
576
     */
577
    public static function sql($query, $param = [])
578
    {
579
        self::instance();
580
581
        try {
582
            self::$_instance->verifyConnection();
583
        } catch (\Exception $e) {
584
            throw new \Exception($e->getMessage());
585
        }
586
587
        if (!is_array($param)) throw new \Exception('Invalid parameter type.');
588
589
        self::$_instance->_current_custom_query_values = $param;
590
591
        $objetos = self::$_instance->query($query, self::$_instance->_current_custom_query_values);
592
593
        self::$_instance->_current_custom_query_values = [];
594
595
        self::$_instance->cleanNewData();
596
597
        return $objetos;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $objetos also could return the type integer|MocOrm\Model\Model which is incompatible with the documented return type array.
Loading history...
598
    }
599
600
    /**
601
     * Get the last execute query from ORM
602
     * @return string on last query
603
     */
604
    final protected function getLastQuery()
605
    {
606
        return $this->Connection->getLastPerformedQuery();
607
    }
608
609
    /**
610
     * Get all execute query from ORM
611
     * @return Array all query executed
612
     */
613
    final protected function getAllPerfomedQuery()
614
    {
615
        return $this->Connection->getPerformedQuery();
616
    }
617
618
    /**
619
     * Execute an closure after insert, update or delete.
620
     * @param $closure
621
     * @throws \Exception If colunm isn't a closure
622
     * @return $this
623
     */
624
    final protected function setTriggerAfter($closure = null)
625
    {
626
        if (!is_callable($closure)) throw new \Exception('The parameter don\'t is an closure.');
627
628
        $this->triggerAfter = $closure;
629
630
        return $this;
631
    }
632
633
    /**
634
     * Execute an closure before insert, update or delete.
635
     * @param $closure
636
     * @throws \Exception If colunm isn't a closure
637
     * @return $this
638
     */
639
    final protected function setTriggerBefore($closure = null)
640
    {
641
        if (!is_callable($closure)) throw new \Exception('The parameter don\'t is an closure.');
642
643
        $this->triggerBefore = $closure;
644
645
        return $this;
646
    }
647
648
    /**
649
     * Change schema on postgres
650
     * @param String $schema schema name
651
     * @return $this
652
     */
653
    final protected function changeSchema($schema)
654
    {
655
        if (!is_string($schema)) throw new \Exception('The parameter don\'t is an String.');
0 ignored issues
show
The condition is_string($schema) is always true.
Loading history...
656
657
        $this->Connection->changeSchema($schema);
658
        return $this;
659
    }
660
661
    /**
662
     * Clean data
663
     * @return $this
664
     */
665
    final protected function cleanNewData()
666
    {
667
        $this->_newData = [];
668
        return $this;
669
    }
670
671
    protected function getData()
672
    {
673
        return $this->_data;
674
    }
675
676
    /**
677
     * Auxiliar method for current instance is set
678
     * @return $this current class
679
     */
680
    final private static function instance()
681
    {
682
        $calledClass = get_called_class();
683
684
        self::$_instance = new $calledClass();
685
686
        return self::$_instance;
687
    }
688
689
    /**
690
     * @throws \Exception if connection doesn't exists.
691
     */
692
    final private function verifyConnection()
693
    {
694
        if (is_null($this->Connection->getCurrentConnectionString())) throw new \Exception('Not set connection.');
0 ignored issues
show
The condition is_null($this->Connectio...rentConnectionString()) is always false.
Loading history...
695
    }
696
697
    /**
698
     * @param String $tableName
699
     * @return $this
700
     */
701
    protected function setTableName($tableName)
702
    {
703
        self::$table_name = $tableName;
704
705
        return $this;
706
    }
707
708
    /**
709
     * @param string $primaryKey
710
     * @return $this
711
     */
712
    protected function setPrimaryKey($primaryKey = 'id')
713
    {
714
        self::$primary_key = $primaryKey;
715
716
        return $this;
717
    }
718
}