Completed
Branch develop (8f72cf)
by John
07:52
created

ActiveRecordProviderMySQL::makeTable()   C

Complexity

Conditions 21
Paths 90

Size

Total Lines 72
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 72
rs 5.4212
c 0
b 0
f 0
cc 21
eloc 50
nc 90
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Integer;
6
use Alpha\Model\Type\Timestamp;
7
use Alpha\Model\Type\DEnum;
8
use Alpha\Model\Type\Relation;
9
use Alpha\Model\Type\RelationLookup;
10
use Alpha\Model\Type\Double;
11
use Alpha\Model\Type\Text;
12
use Alpha\Model\Type\SmallText;
13
use Alpha\Model\Type\Date;
14
use Alpha\Model\Type\Enum;
15
use Alpha\Model\Type\Boolean;
16
use Alpha\Util\Config\ConfigProvider;
17
use Alpha\Util\Logging\Logger;
18
use Alpha\Util\Helper\Validator;
19
use Alpha\Util\Http\Session\SessionProviderFactory;
20
use Alpha\Exception\AlphaException;
21
use Alpha\Exception\FailedSaveException;
22
use Alpha\Exception\FailedDeleteException;
23
use Alpha\Exception\FailedIndexCreateException;
24
use Alpha\Exception\LockingException;
25
use Alpha\Exception\ValidationException;
26
use Alpha\Exception\CustomQueryException;
27
use Alpha\Exception\RecordNotFoundException;
28
use Alpha\Exception\BadTableNameException;
29
use Alpha\Exception\ResourceNotAllowedException;
30
use Alpha\Exception\IllegalArguementException;
31
use Alpha\Exception\PHPException;
32
use Exception;
33
use ReflectionClass;
34
use Mysqli;
35
36
/**
37
 * MySQL active record provider (uses the MySQLi native API in PHP).
38
 *
39
 * @since 1.1
40
 *
41
 * @author John Collins <[email protected]>
42
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
43
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
44
 * All rights reserved.
45
 *
46
 * <pre>
47
 * Redistribution and use in source and binary forms, with or
48
 * without modification, are permitted provided that the
49
 * following conditions are met:
50
 *
51
 * * Redistributions of source code must retain the above
52
 *   copyright notice, this list of conditions and the
53
 *   following disclaimer.
54
 * * Redistributions in binary form must reproduce the above
55
 *   copyright notice, this list of conditions and the
56
 *   following disclaimer in the documentation and/or other
57
 *   materials provided with the distribution.
58
 * * Neither the name of the Alpha Framework nor the names
59
 *   of its contributors may be used to endorse or promote
60
 *   products derived from this software without specific
61
 *   prior written permission.
62
 *
63
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
64
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
65
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
66
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
67
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
68
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
69
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
70
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
71
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
72
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
73
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
74
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
75
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
76
 * </pre>
77
 */
78
class ActiveRecordProviderMySQL implements ActiveRecordProviderInterface
79
{
80
    /**
81
     * Trace logger.
82
     *
83
     * @var \Alpha\Util\Logging\Logger
84
     *
85
     * @since 1.1
86
     */
87
    private static $logger = null;
88
89
    /**
90
     * Datebase connection.
91
     *
92
     * @var Mysqli
93
     *
94
     * @since 1.1
95
     */
96
    private static $connection;
97
98
    /**
99
     * The business object that we are mapping back to.
100
     *
101
     * @var \Alpha\Model\ActiveRecord
102
     *
103
     * @since 1.1
104
     */
105
    private $BO;
106
107
    /**
108
     * The constructor.
109
     *
110
     * @since 1.1
111
     */
112
    public function __construct()
113
    {
114
        self::$logger = new Logger('ActiveRecordProviderMySQL');
115
        self::$logger->debug('>>__construct()');
116
117
        self::$logger->debug('<<__construct');
118
    }
119
120
    /**
121
     * (non-PHPdoc).
122
     *
123
     * @see Alpha\Model\ActiveRecordProviderInterface::getConnection()
124
     */
125
    public static function getConnection()
126
    {
127
        $config = ConfigProvider::getInstance();
128
129
        if (!isset(self::$connection)) {
130
            try {
131
                self::$connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'), $config->get('db.name'));
132
            } catch (\Exception $e) {
133
                // if we failed to connect because the database does not exist, create it and try again
134
                if (strpos($e->getMessage(), 'HY000/1049') !== false) {
135
                    self::createDatabase();
136
                    self::$connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'), $config->get('db.name'));
137
                }
138
            }
139
140
            self::$connection->set_charset('utf8');
141
142
            if (mysqli_connect_error()) {
143
                self::$logger->fatal('Could not connect to database: ['.mysqli_connect_errno().'] '.mysqli_connect_error());
144
            }
145
        }
146
147
        return self::$connection;
148
    }
149
150
    /**
151
     * (non-PHPdoc).
152
     *
153
     * @see Alpha\Model\ActiveRecordProviderInterface::disconnect()
154
     */
155
    public static function disconnect()
156
    {
157
        if (isset(self::$connection)) {
158
            self::$connection->close();
159
            self::$connection = null;
160
        }
161
    }
162
163
    /**
164
     * (non-PHPdoc).
165
     *
166
     * @see Alpha\Model\ActiveRecordProviderInterface::getLastDatabaseError()
167
     */
168
    public static function getLastDatabaseError()
169
    {
170
        return self::getConnection()->error;
171
    }
172
173
    /**
174
     * (non-PHPdoc).
175
     *
176
     * @see Alpha\Model\ActiveRecordProviderInterface::query()
177
     */
178
    public function query($sqlQuery)
179
    {
180
        $this->BO->setLastQuery($sqlQuery);
181
182
        $resultArray = array();
183
184
        if (!$result = self::getConnection()->query($sqlQuery)) {
185
            throw new CustomQueryException('Failed to run the custom query, MySql error is ['.self::getConnection()->error.'], query ['.$sqlQuery.']');
186
        } else {
187
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
188
                array_push($resultArray, $row);
189
            }
190
191
            return $resultArray;
192
        }
193
    }
194
195
    /**
196
     * (non-PHPdoc).
197
     *
198
     * @see Alpha\Model\ActiveRecordProviderInterface::load()
199
     */
200
    public function load($OID, $version = 0)
201
    {
202
        self::$logger->debug('>>load(OID=['.$OID.'], version=['.$version.'])');
203
204
        $attributes = $this->BO->getPersistentAttributes();
205
        $fields = '';
206
        foreach ($attributes as $att) {
207
            $fields .= $att.',';
208
        }
209
        $fields = mb_substr($fields, 0, -1);
210
211
        if ($version > 0) {
212
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->BO->getTableName().'_history WHERE OID = ? AND version_num = ? LIMIT 1;';
213
        } else {
214
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->BO->getTableName().' WHERE OID = ? LIMIT 1;';
215
        }
216
        $this->BO->setLastQuery($sqlQuery);
217
        $stmt = self::getConnection()->stmt_init();
218
219
        $row = array();
220
221
        if ($stmt->prepare($sqlQuery)) {
222
            if ($version > 0) {
223
                $stmt->bind_param('ii', $OID, $version);
224
            } else {
225
                $stmt->bind_param('i', $OID);
226
            }
227
228
            $stmt->execute();
229
230
            $result = $this->bindResult($stmt);
231
            if (isset($result[0])) {
232
                $row = $result[0];
233
            }
234
235
            $stmt->close();
236
        } else {
237
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.'], OID is ['.print_r($OID, true).'], MySql error is ['.self::getConnection()->error.']');
238
            if (!$this->BO->checkTableExists()) {
239
                $this->BO->makeTable();
240
241
                throw new RecordNotFoundException('Failed to load object of OID ['.$OID.'], table ['.$this->BO->getTableName().'] did not exist so had to create!');
242
            }
243
244
            return;
245
        }
246
247
        if (!isset($row['OID']) || $row['OID'] < 1) {
248
            self::$logger->debug('<<load');
249
            throw new RecordNotFoundException('Failed to load object of OID ['.$OID.'] not found in database.');
250
        }
251
252
        // get the class attributes
253
        $reflection = new ReflectionClass(get_class($this->BO));
254
        $properties = $reflection->getProperties();
255
256
        try {
257
            foreach ($properties as $propObj) {
258
                $propName = $propObj->name;
259
260
                // filter transient attributes
261
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
262
                    $this->BO->set($propName, $row[$propName]);
263
                } elseif (!$propObj->isPrivate() && $this->BO->getPropObject($propName) instanceof Relation) {
264
                    $prop = $this->BO->getPropObject($propName);
265
266
                    // handle the setting of ONE-TO-MANY relation values
267
                    if ($prop->getRelationType() == 'ONE-TO-MANY') {
268
                        $this->BO->set($propObj->name, $this->BO->getOID());
269
                    }
270
271
                    // handle the setting of MANY-TO-ONE relation values
272
                    if ($prop->getRelationType() == 'MANY-TO-ONE' && isset($row[$propName])) {
273
                        $this->BO->set($propObj->name, $row[$propName]);
274
                    }
275
                }
276
            }
277
        } catch (IllegalArguementException $e) {
278
            self::$logger->warn('Bad data stored in the table ['.$this->BO->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
279
        } catch (PHPException $e) {
280
            // it is possible that the load failed due to the table not being up-to-date
281
            if ($this->BO->checkTableNeedsUpdate()) {
282
                $missingFields = $this->BO->findMissingFields();
283
284
                $count = count($missingFields);
285
286
                for ($i = 0; $i < $count; ++$i) {
287
                    $this->BO->addProperty($missingFields[$i]);
288
                }
289
290
                self::$logger->warn('<<load');
291
                throw new RecordNotFoundException('Failed to load object of OID ['.$OID.'], table ['.$this->BO->getTableName().'] was out of sync with the database so had to be updated!');
292
            }
293
        }
294
295
        self::$logger->debug('<<load ['.$OID.']');
296
    }
297
298
    /**
299
     * (non-PHPdoc).
300
     *
301
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllOldVersions()
302
     */
303
    public function loadAllOldVersions($OID)
304
    {
305
        self::$logger->debug('>>loadAllOldVersions(OID=['.$OID.'])');
306
307
        if (!$this->BO->getMaintainHistory()) {
308
            throw new RecordFoundException('loadAllOldVersions method called on an active record where no history is maintained!');
309
        }
310
311
        $sqlQuery = 'SELECT version_num FROM '.$this->BO->getTableName().'_history WHERE OID = \''.$OID.'\' ORDER BY version_num;';
312
313
        $this->BO->setLastQuery($sqlQuery);
314
315
        if (!$result = self::getConnection()->query($sqlQuery)) {
316
            self::$logger->debug('<<loadAllOldVersions [0]');
317
            throw new RecordNotFoundException('Failed to load object versions, MySQL error is ['.self::getLastDatabaseError().'], query ['.$this->BO->getLastQuery().']');
318
        }
319
320
        // now build an array of objects to be returned
321
        $objects = array();
322
        $count = 0;
323
        $RecordClass = get_class($this->BO);
324
325
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
326
            try {
327
                $obj = new $RecordClass();
328
                $obj->load($OID, $row['version_num']);
329
                $objects[$count] = $obj;
330
                ++$count;
331
            } catch (ResourceNotAllowedException $e) {
332
                // the resource not allowed will be absent from the list
333
            }
334
        }
335
336
        self::$logger->debug('<<loadAllOldVersions ['.count($objects).']');
337
338
        return $objects;
339
    }
340
341
    /**
342
     * (non-PHPdoc).
343
     *
344
     * @see Alpha\Model\ActiveRecordProviderInterface::loadByAttribute()
345
     */
346
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
347
    {
348
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'],
349
			loadAttributes=['.var_export($loadAttributes, true).'])');
350
351
        if (count($loadAttributes) == 0) {
352
            $attributes = $this->BO->getPersistentAttributes();
353
        } else {
354
            $attributes = $loadAttributes;
355
        }
356
357
        $fields = '';
358
        foreach ($attributes as $att) {
359
            $fields .= $att.',';
360
        }
361
        $fields = mb_substr($fields, 0, -1);
362
363
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
364
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->BO->getTableName().' WHERE '.$attribute.' = ? AND classname = ? LIMIT 1;';
365
        } else {
366
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->BO->getTableName().' WHERE '.$attribute.' = ? LIMIT 1;';
367
        }
368
369
        self::$logger->debug('Query=['.$sqlQuery.']');
370
371
        $this->BO->setLastQuery($sqlQuery);
372
        $stmt = self::getConnection()->stmt_init();
373
374
        $row = array();
375
376
        if ($stmt->prepare($sqlQuery)) {
377
            if ($this->BO->getPropObject($attribute) instanceof Integer) {
378
                if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
379
                    $classname = get_class($this->BO);
380
                    $stmt->bind_param('is', $value, $classname);
381
                } else {
382
                    $stmt->bind_param('i', $value);
383
                }
384
            } else {
385
                if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
386
                    $classname = get_class($this->BO);
387
                    $stmt->bind_param('ss', $value, $classname);
388
                } else {
389
                    $stmt->bind_param('s', $value);
390
                }
391
            }
392
393
            $stmt->execute();
394
395
            $result = $this->bindResult($stmt);
396
397
            if (isset($result[0])) {
398
                $row = $result[0];
399
            }
400
401
            $stmt->close();
402
        } else {
403
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
404
            if (!$this->BO->checkTableExists()) {
405
                $this->BO->makeTable();
406
407
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
408
            }
409
410
            return;
411
        }
412
413
        if (!isset($row['OID']) || $row['OID'] < 1) {
414
            self::$logger->debug('<<loadByAttribute');
415
            throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], not found in database.');
416
        }
417
418
        $this->BO->setOID($row['OID']);
419
420
        // get the class attributes
421
        $reflection = new ReflectionClass(get_class($this->BO));
422
        $properties = $reflection->getProperties();
423
424
        try {
425
            foreach ($properties as $propObj) {
426
                $propName = $propObj->name;
427
428
                if (isset($row[$propName])) {
429
                    // filter transient attributes
430
                    if (!in_array($propName, $this->BO->getTransientAttributes())) {
431
                        $this->BO->set($propName, $row[$propName]);
432
                    } elseif (!$propObj->isPrivate() && $this->BO->get($propName) != '' && $this->BO->getPropObject($propName) instanceof Relation) {
433
                        $prop = $this->BO->getPropObject($propName);
434
435
                        // handle the setting of ONE-TO-MANY relation values
436
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
437
                            $this->BO->set($propObj->name, $this->BO->getOID());
438
                        }
439
                    }
440
                }
441
            }
442
        } catch (IllegalArguementException $e) {
443
            self::$logger->warn('Bad data stored in the table ['.$this->BO->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
444
        } catch (PHPException $e) {
445
            // it is possible that the load failed due to the table not being up-to-date
446
            if ($this->BO->checkTableNeedsUpdate()) {
447
                $missingFields = $this->BO->findMissingFields();
448
449
                $count = count($missingFields);
450
451
                for ($i = 0; $i < $count; ++$i) {
452
                    $this->BO->addProperty($missingFields[$i]);
453
                }
454
455
                self::$logger->debug('<<loadByAttribute');
456
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table ['.$this->BO->getTableName().'] was out of sync with the database so had to be updated!');
457
            }
458
        }
459
460
        self::$logger->debug('<<loadByAttribute');
461
    }
462
463
    /**
464
     * (non-PHPdoc).
465
     *
466
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAll()
467
     */
468
    public function loadAll($start = 0, $limit = 0, $orderBy = 'OID', $order = 'ASC', $ignoreClassType = false)
469
    {
470
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
471
472
        // ensure that the field name provided in the orderBy param is legit
473
        try {
474
            $this->BO->get($orderBy);
475
        } catch (AlphaException $e) {
476
            throw new AlphaException('The field name ['.$orderBy.'] provided in the param orderBy does not exist on the class ['.get_class($this->BO).']');
477
        }
478
479
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
480
            if ($limit == 0) {
481
                $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' WHERE classname = \''.addslashes(get_class($this->BO)).'\' ORDER BY '.$orderBy.' '.$order.';';
482
            } else {
483
                $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' WHERE classname = \''.addslashes(get_class($this->BO)).'\' ORDER BY '.$orderBy.' '.$order.' LIMIT '.
484
                    $start.', '.$limit.';';
485
            }
486
        } else {
487
            if ($limit == 0) {
488
                $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' ORDER BY '.$orderBy.' '.$order.';';
489
            } else {
490
                $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' ORDER BY '.$orderBy.' '.$order.' LIMIT '.$start.', '.$limit.';';
491
            }
492
        }
493
494
        $this->BO->setLastQuery($sqlQuery);
495
496
        if (!$result = self::getConnection()->query($sqlQuery)) {
497
            self::$logger->debug('<<loadAll [0]');
498
            throw new RecordNotFoundException('Failed to load object OIDs, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
499
        }
500
501
        // now build an array of objects to be returned
502
        $objects = array();
503
        $count = 0;
504
        $RecordClass = get_class($this->BO);
505
506
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
507
            try {
508
                $obj = new $RecordClass();
509
                $obj->load($row['OID']);
510
                $objects[$count] = $obj;
511
                ++$count;
512
            } catch (ResourceNotAllowedException $e) {
513
                // the resource not allowed will be absent from the list
514
            }
515
        }
516
517
        self::$logger->debug('<<loadAll ['.count($objects).']');
518
519
        return $objects;
520
    }
521
522
    /**
523
     * (non-PHPdoc).
524
     *
525
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttribute()
526
     */
527
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'OID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
528
    {
529
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
530
531
        if ($limit != 0) {
532
            $limit = ' LIMIT '.$start.', '.$limit.';';
533
        } else {
534
            $limit = ';';
535
        }
536
537
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
538
            $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName()." WHERE $attribute = ? AND classname = ? ORDER BY ".$orderBy.' '.$order.$limit;
539
        } else {
540
            $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName()." WHERE $attribute = ? ORDER BY ".$orderBy.' '.$order.$limit;
541
        }
542
543
        $this->BO->setLastQuery($sqlQuery);
544
        self::$logger->debug($sqlQuery);
545
546
        $stmt = self::getConnection()->stmt_init();
547
548
        $row = array();
549
550
        if ($stmt->prepare($sqlQuery)) {
551
            if ($this->BO->getPropObject($attribute) instanceof Integer) {
552
                if ($this->BO->isTableOverloaded()) {
553
                    $classname = get_class($this->BO);
554
                    $stmt->bind_param('is', $value, $classname);
555
                } else {
556
                    $stmt->bind_param('i', $value);
557
                }
558
            } else {
559
                if ($this->BO->isTableOverloaded()) {
560
                    $classname = get_class($this->BO);
561
                    $stmt->bind_param('ss', $value, $classname);
562
                } else {
563
                    $stmt->bind_param('s', $value);
564
                }
565
            }
566
567
            $stmt->execute();
568
569
            $result = $this->bindResult($stmt);
570
571
            $stmt->close();
572
        } else {
573
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
574
            if (!$this->BO->checkTableExists()) {
575
                $this->BO->makeTable();
576
577
                throw new RecordNotFoundException('Failed to load objects by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
578
            }
579
            self::$logger->debug('<<loadAllByAttribute []');
580
581
            return array();
582
        }
583
584
        // now build an array of objects to be returned
585
        $objects = array();
586
        $count = 0;
587
        $RecordClass = get_class($this->BO);
588
589
        foreach ($result as $row) {
590
            try {
591
                $argsCount = count($constructorArgs);
592
593
                if ($argsCount < 1) {
594
                    $obj = new $RecordClass();
595
                } else {
596
                    switch ($argsCount) {
597
                        case 1:
598
                            $obj = new $RecordClass($constructorArgs[0]);
599
                        break;
600
                        case 2:
601
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
602
                        break;
603
                        case 3:
604
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
605
                        break;
606
                        case 4:
607
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
608
                        break;
609
                        case 5:
610
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
611
                        break;
612
                        default:
613
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
614
                    }
615
                }
616
617
                $obj->load($row['OID']);
618
                $objects[$count] = $obj;
619
                ++$count;
620
            } catch (ResourceNotAllowedException $e) {
621
                // the resource not allowed will be absent from the list
622
            }
623
        }
624
625
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
626
627
        return $objects;
628
    }
629
630
    /**
631
     * (non-PHPdoc).
632
     *
633
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttributes()
634
     */
635
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'OID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
636
    {
637
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
638
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
639
640
        $whereClause = ' WHERE';
641
642
        $count = count($attributes);
643
644
        for ($i = 0; $i < $count; ++$i) {
645
            $whereClause .= ' '.$attributes[$i].' = ? AND';
646
            self::$logger->debug($whereClause);
647
        }
648
649
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
650
            $whereClause .= ' classname = ? AND';
651
        }
652
653
        // remove the last " AND"
654
        $whereClause = mb_substr($whereClause, 0, -4);
655
656
        if ($limit != 0) {
657
            $limit = ' LIMIT '.$start.', '.$limit.';';
658
        } else {
659
            $limit = ';';
660
        }
661
662
        $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().$whereClause.' ORDER BY '.$orderBy.' '.$order.$limit;
663
664
        $this->BO->setLastQuery($sqlQuery);
665
666
        $stmt = self::getConnection()->stmt_init();
667
668
        if ($stmt->prepare($sqlQuery)) {
669
            // bind params where required attributes are provided
670
            if (count($attributes) > 0 && count($attributes) == count($values)) {
671
                $stmt = $this->bindParams($stmt, $attributes, $values);
0 ignored issues
show
Documentation introduced by
$stmt is of type object<mysqli_stmt>, but the function expects a object<Alpha\Model\mysqli_stmt>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
672
            } else {
673
                // we'll still need to bind the "classname" for overloaded BOs...
674
                if ($this->BO->isTableOverloaded()) {
675
                    $classname = get_class($this->BO);
676
                    $stmt->bind_param('s', $classname);
677
                }
678
            }
679
            $stmt->execute();
680
681
            $result = $this->bindResult($stmt);
682
683
            $stmt->close();
684
        } else {
685
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
686
687
            if (!$this->BO->checkTableExists()) {
688
                $this->BO->makeTable();
689
690
                throw new RecordNotFoundException('Failed to load objects by attributes ['.var_export($attributes, true).'] and values ['.
691
                    var_export($values, true).'], table did not exist so had to create!');
692
            }
693
694
            self::$logger->debug('<<loadAllByAttributes []');
695
696
            return array();
697
        }
698
699
        // now build an array of objects to be returned
700
        $objects = array();
701
        $count = 0;
702
        $RecordClass = get_class($this->BO);
703
704
        foreach ($result as $row) {
705
            try {
706
                $argsCount = count($constructorArgs);
707
708
                if ($argsCount < 1) {
709
                    $obj = new $RecordClass();
710
                } else {
711
                    switch ($argsCount) {
712
                        case 1:
713
                            $obj = new $RecordClass($constructorArgs[0]);
714
                        break;
715
                        case 2:
716
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
717
                        break;
718
                        case 3:
719
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
720
                        break;
721
                        case 4:
722
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
723
                        break;
724
                        case 5:
725
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
726
                        break;
727
                        default:
728
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
729
                    }
730
                }
731
732
                $obj->load($row['OID']);
733
                $objects[$count] = $obj;
734
                ++$count;
735
            } catch (ResourceNotAllowedException $e) {
736
                // the resource not allowed will be absent from the list
737
            }
738
        }
739
740
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
741
742
        return $objects;
743
    }
744
745
    /**
746
     * (non-PHPdoc).
747
     *
748
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByDayUpdated()
749
     */
750
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'OID', $order = 'ASC', $ignoreClassType = false)
751
    {
752
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
753
754
        if ($start != 0 && $limit != 0) {
755
            $limit = ' LIMIT '.$start.', '.$limit.';';
756
        } else {
757
            $limit = ';';
758
        }
759
760
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
761
            $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' AND classname = '".addslashes(get_class($this->BO))."' ORDER BY ".$orderBy.' '.$order.$limit;
762
        } else {
763
            $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' ORDER BY ".$orderBy.' '.$order.$limit;
764
        }
765
766
        $this->BO->setLastQuery($sqlQuery);
767
768
        if (!$result = self::getConnection()->query($sqlQuery)) {
769
            self::$logger->debug('<<loadAllByDayUpdated []');
770
            throw new RecordNotFoundException('Failed to load object OIDs, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
771
        }
772
773
        // now build an array of objects to be returned
774
        $objects = array();
775
        $count = 0;
776
        $RecordClass = get_class($this->BO);
777
778
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
779
            $obj = new $RecordClass();
780
            $obj->load($row['OID']);
781
            $objects[$count] = $obj;
782
            ++$count;
783
        }
784
785
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
786
787
        return $objects;
788
    }
789
790
    /**
791
     * (non-PHPdoc).
792
     *
793
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllFieldValuesByAttribute()
794
     */
795
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
796
    {
797
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
798
799
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
800
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->BO->getTableName()." WHERE $attribute = '$value' AND classname = '".addslashes(get_class($this->BO))."' ORDER BY OID ".$order.';';
801
        } else {
802
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->BO->getTableName()." WHERE $attribute = '$value' ORDER BY OID ".$order.';';
803
        }
804
805
        $this->BO->setLastQuery($sqlQuery);
806
807
        self::$logger->debug('lastQuery ['.$sqlQuery.']');
808
809
        if (!$result = self::getConnection()->query($sqlQuery)) {
810
            self::$logger->debug('<<loadAllFieldValuesByAttribute []');
811
            throw new RecordNotFoundException('Failed to load field ['.$returnAttribute.'] values, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
812
        }
813
814
        // now build an array of attribute values to be returned
815
        $values = array();
816
        $count = 0;
817
818
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
819
            $values[$count] = $row[$returnAttribute];
820
            ++$count;
821
        }
822
823
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
824
825
        return $values;
826
    }
827
828
    /**
829
     * (non-PHPdoc).
830
     *
831
     * @see Alpha\Model\ActiveRecordProviderInterface::save()
832
     */
833
    public function save()
834
    {
835
        self::$logger->debug('>>save()');
836
837
        $config = ConfigProvider::getInstance();
838
        $sessionProvider = $config->get('session.provider.name');
839
        $session = SessionProviderFactory::getInstance($sessionProvider);
840
841
        // get the class attributes
842
        $reflection = new ReflectionClass(get_class($this->BO));
843
        $properties = $reflection->getProperties();
844
845
        if ($this->BO->getVersion() != $this->BO->getVersionNumber()->getValue()) {
846
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
847
        }
848
849
        // set the "updated by" fields, we can only set the user id if someone is logged in
850
        if ($session->get('currentUser') != null) {
851
            $this->BO->set('updated_by', $session->get('currentUser')->getOID());
852
        }
853
854
        $this->BO->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
855
856
        // check to see if it is a transient object that needs to be inserted
857
        if ($this->BO->isTransient()) {
858
            $savedFieldsCount = 0;
859
            $sqlQuery = 'INSERT INTO '.$this->BO->getTableName().' (';
860
861
            foreach ($properties as $propObj) {
862
                $propName = $propObj->name;
863
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
864
                    // Skip the OID, database auto number takes care of this.
865
                    if ($propName != 'OID' && $propName != 'version_num') {
866
                        $sqlQuery .= "$propName,";
867
                        ++$savedFieldsCount;
868
                    }
869
870
                    if ($propName == 'version_num') {
871
                        $sqlQuery .= 'version_num,';
872
                        ++$savedFieldsCount;
873
                    }
874
                }
875
            }
876
877
            if ($this->BO->isTableOverloaded()) {
878
                $sqlQuery .= 'classname,';
879
            }
880
881
            $sqlQuery = rtrim($sqlQuery, ',');
882
883
            $sqlQuery .= ') VALUES (';
884
885
            for ($i = 0; $i < $savedFieldsCount; ++$i) {
886
                $sqlQuery .= '?,';
887
            }
888
889
            if ($this->BO->isTableOverloaded()) {
890
                $sqlQuery .= '?,';
891
            }
892
893
            $sqlQuery = rtrim($sqlQuery, ',').')';
894
895
            $this->BO->setLastQuery($sqlQuery);
896
            self::$logger->debug('Query ['.$sqlQuery.']');
897
898
            $stmt = self::getConnection()->stmt_init();
899
900
            if ($stmt->prepare($sqlQuery)) {
901
                $stmt = $this->bindParams($stmt);
0 ignored issues
show
Documentation introduced by
$stmt is of type object<mysqli_stmt>, but the function expects a object<Alpha\Model\mysqli_stmt>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
902
                $stmt->execute();
903
            } else {
904
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
905
            }
906
        } else {
907
            // assume that it is a persistent object that needs to be updated
908
            $savedFieldsCount = 0;
909
            $sqlQuery = 'UPDATE '.$this->BO->getTableName().' SET ';
910
911
            foreach ($properties as $propObj) {
912
                $propName = $propObj->name;
913
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
914
                    // Skip the OID, database auto number takes care of this.
915
                    if ($propName != 'OID' && $propName != 'version_num') {
916
                        $sqlQuery .= "$propName = ?,";
917
                        ++$savedFieldsCount;
918
                    }
919
920
                    if ($propName == 'version_num') {
921
                        $sqlQuery .= 'version_num = ?,';
922
                        ++$savedFieldsCount;
923
                    }
924
                }
925
            }
926
927
            if ($this->BO->isTableOverloaded()) {
928
                $sqlQuery .= 'classname = ?,';
929
            }
930
931
            $sqlQuery = rtrim($sqlQuery, ',');
932
933
            $sqlQuery .= ' WHERE OID=?;';
934
935
            $this->BO->setLastQuery($sqlQuery);
936
            $stmt = self::getConnection()->stmt_init();
937
938
            if ($stmt->prepare($sqlQuery)) {
939
                $this->bindParams($stmt);
0 ignored issues
show
Documentation introduced by
$stmt is of type object<mysqli_stmt>, but the function expects a object<Alpha\Model\mysqli_stmt>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
940
                $stmt->execute();
941
            } else {
942
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
943
            }
944
        }
945
946
        if ($stmt != null && $stmt->error == '') {
947
            // populate the updated OID in case we just done an insert
948
            if ($this->BO->isTransient()) {
949
                $this->BO->setOID(self::getConnection()->insert_id);
950
            }
951
952
            try {
953
                foreach ($properties as $propObj) {
954
                    $propName = $propObj->name;
955
956
                    if ($this->BO->getPropObject($propName) instanceof Relation) {
957
                        $prop = $this->BO->getPropObject($propName);
958
959
                        // handle the saving of MANY-TO-MANY relation values
960
                        if ($prop->getRelationType() == 'MANY-TO-MANY' && count($prop->getRelatedOIDs()) > 0) {
961
                            try {
962
                                try {
963
                                    // check to see if the rel is on this class
964
                                    $side = $prop->getSide(get_class($this->BO));
965
                                } catch (IllegalArguementException $iae) {
966
                                    $side = $prop->getSide(get_parent_class($this->BO));
967
                                }
968
969
                                $lookUp = $prop->getLookup();
970
971
                                // first delete all of the old RelationLookup objects for this rel
972
                                try {
973
                                    if ($side == 'left') {
974
                                        $lookUp->deleteAllByAttribute('leftID', $this->BO->getOID());
975
                                    } else {
976
                                        $lookUp->deleteAllByAttribute('rightID', $this->BO->getOID());
977
                                    }
978
                                } catch (\Exception $e) {
979
                                    throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
980
                                }
981
982
                                $OIDs = $prop->getRelatedOIDs();
983
984
                                if (isset($OIDs) && !empty($OIDs[0])) {
985
                                    // now for each posted OID, create a new RelationLookup record and save
986
                                    foreach ($OIDs as $oid) {
987
                                        $newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
988
                                        if ($side == 'left') {
989
                                            $newLookUp->set('leftID', $this->BO->getOID());
990
                                            $newLookUp->set('rightID', $oid);
991
                                        } else {
992
                                            $newLookUp->set('rightID', $this->BO->getOID());
993
                                            $newLookUp->set('leftID', $oid);
994
                                        }
995
                                        $newLookUp->save();
996
                                    }
997
                                }
998
                            } catch (\Exception $e) {
999
                                throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
1000
                            }
1001
                        }
1002
1003
                        // handle the saving of ONE-TO-MANY relation values
1004
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
1005
                            $prop->setValue($this->BO->getOID());
1006
                        }
1007
                    }
1008
                }
1009
            } catch (\Exception $e) {
1010
                throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
1011
            }
1012
1013
            $stmt->close();
1014
        } else {
1015
            // there has been an error, so decrement the version number back
1016
            $temp = $this->BO->getVersionNumber()->getValue();
1017
            $this->BO->set('version_num', $temp - 1);
1018
1019
            // check for unique violations
1020
            if (self::getConnection()->errno == '1062') {
1021
                throw new ValidationException('Failed to save, the value '.$this->findOffendingValue(self::getConnection()->error).' is already in use!');
1022
            } else {
1023
                throw new FailedSaveException('Failed to save object, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
1024
            }
1025
        }
1026
1027
        if ($this->BO->getMaintainHistory()) {
1028
            $this->BO->saveHistory();
1029
        }
1030
    }
1031
1032
    /**
1033
     * (non-PHPdoc).
1034
     *
1035
     * @see Alpha\Model\ActiveRecordProviderInterface::saveAttribute()
1036
     */
1037
    public function saveAttribute($attribute, $value)
1038
    {
1039
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
1040
1041
        $config = ConfigProvider::getInstance();
1042
        $sessionProvider = $config->get('session.provider.name');
1043
        $session = SessionProviderFactory::getInstance($sessionProvider);
1044
1045
        // get the class attributes
1046
        $reflection = new ReflectionClass(get_class($this->BO));
1047
        $properties = $reflection->getProperties();
0 ignored issues
show
Unused Code introduced by
$properties is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1048
1049
        if ($this->BO->getVersion() != $this->BO->getVersionNumber()->getValue()) {
1050
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
1051
        }
1052
1053
        // set the "updated by" fields, we can only set the user id if someone is logged in
1054
        if ($session->get('currentUser') != null) {
1055
            $this->BO->set('updated_by', $session->get('currentUser')->getOID());
1056
        }
1057
1058
        $this->BO->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
1059
1060
        // assume that it is a persistent object that needs to be updated
1061
        $sqlQuery = 'UPDATE '.$this->BO->getTableName().' SET '.$attribute.' = ?, version_num = ? , updated_by = ?, updated_ts = ? WHERE OID = ?;';
1062
1063
        $this->BO->setLastQuery($sqlQuery);
1064
        $stmt = self::getConnection()->stmt_init();
1065
1066
        $newVersionNumber = $this->BO->getVersionNumber()->getValue() + 1;
1067
1068
        if ($stmt->prepare($sqlQuery)) {
1069
            if ($this->BO->getPropObject($attribute) instanceof Integer) {
1070
                $bindingsType = 'i';
1071
            } else {
1072
                $bindingsType = 's';
1073
            }
1074
            $OID = $this->BO->getOID();
1075
            $updatedBy = $this->BO->get('updated_by');
1076
            $updatedTS = $this->BO->get('updated_ts');
1077
            $stmt->bind_param($bindingsType.'iisi', $value, $newVersionNumber, $updatedBy, $updatedTS, $OID);
1078
            self::$logger->debug('Binding params ['.$bindingsType.'iisi, '.$value.', '.$newVersionNumber.', '.$updatedBy.', '.$updatedTS.', '.$OID.']');
1079
            $stmt->execute();
1080
        } else {
1081
            throw new FailedSaveException('Failed to save attribute, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1082
        }
1083
1084
        $stmt->close();
1085
1086
        $this->BO->set($attribute, $value);
1087
        $this->BO->set('version_num', $newVersionNumber);
1088
1089
        if ($this->BO->getMaintainHistory()) {
1090
            $this->BO->saveHistory();
1091
        }
1092
1093
        self::$logger->debug('<<saveAttribute');
1094
    }
1095
1096
    /**
1097
     * (non-PHPdoc).
1098
     *
1099
     * @see Alpha\Model\ActiveRecordProviderInterface::saveHistory()
1100
     */
1101
    public function saveHistory()
1102
    {
1103
        self::$logger->debug('>>saveHistory()');
1104
1105
        // get the class attributes
1106
        $reflection = new ReflectionClass(get_class($this->BO));
1107
        $properties = $reflection->getProperties();
1108
1109
        $savedFieldsCount = 0;
1110
        $attributeNames = array();
1111
        $attributeValues = array();
1112
1113
        $sqlQuery = 'INSERT INTO '.$this->BO->getTableName().'_history (';
1114
1115
        foreach ($properties as $propObj) {
1116
            $propName = $propObj->name;
1117
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1118
                $sqlQuery .= "$propName,";
1119
                $attributeNames[] = $propName;
1120
                $attributeValues[] = $this->BO->get($propName);
1121
                ++$savedFieldsCount;
1122
            }
1123
        }
1124
1125
        if ($this->BO->isTableOverloaded()) {
1126
            $sqlQuery .= 'classname,';
1127
        }
1128
1129
        $sqlQuery = rtrim($sqlQuery, ',');
1130
1131
        $sqlQuery .= ') VALUES (';
1132
1133
        for ($i = 0; $i < $savedFieldsCount; ++$i) {
1134
            $sqlQuery .= '?,';
1135
        }
1136
1137
        if ($this->BO->isTableOverloaded()) {
1138
            $sqlQuery .= '?,';
1139
        }
1140
1141
        $sqlQuery = rtrim($sqlQuery, ',').')';
1142
1143
        $this->BO->setLastQuery($sqlQuery);
1144
        self::$logger->debug('Query ['.$sqlQuery.']');
1145
1146
        $stmt = self::getConnection()->stmt_init();
1147
1148
        if ($stmt->prepare($sqlQuery)) {
1149
            $stmt = $this->bindParams($stmt, $attributeNames, $attributeValues);
0 ignored issues
show
Documentation introduced by
$stmt is of type object<mysqli_stmt>, but the function expects a object<Alpha\Model\mysqli_stmt>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1150
            $stmt->execute();
1151
        } else {
1152
            throw new FailedSaveException('Failed to save object history, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1153
        }
1154
    }
1155
1156
    /**
1157
     * (non-PHPdoc).
1158
     *
1159
     * @see Alpha\Model\ActiveRecordProviderInterface::delete()
1160
     */
1161
    public function delete()
1162
    {
1163
        self::$logger->debug('>>delete()');
1164
1165
        $sqlQuery = 'DELETE FROM '.$this->BO->getTableName().' WHERE OID = ?;';
1166
1167
        $this->BO->setLastQuery($sqlQuery);
1168
1169
        $stmt = self::getConnection()->stmt_init();
1170
1171
        if ($stmt->prepare($sqlQuery)) {
1172
            $OID = $this->BO->getOID();
1173
            $stmt->bind_param('i', $OID);
1174
            $stmt->execute();
1175
            self::$logger->debug('Deleted the object ['.$this->BO->getOID().'] of class ['.get_class($this->BO).']');
1176
        } else {
1177
            throw new FailedDeleteException('Failed to delete object ['.$this->BO->getOID().'], error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1178
        }
1179
1180
        $stmt->close();
1181
1182
        self::$logger->debug('<<delete');
1183
    }
1184
1185
    /**
1186
     * (non-PHPdoc).
1187
     *
1188
     * @see Alpha\Model\ActiveRecordProviderInterface::getVersion()
1189
     */
1190
    public function getVersion()
1191
    {
1192
        self::$logger->debug('>>getVersion()');
1193
1194
        $sqlQuery = 'SELECT version_num FROM '.$this->BO->getTableName().' WHERE OID = ?;';
1195
        $this->BO->setLastQuery($sqlQuery);
1196
1197
        $stmt = self::getConnection()->stmt_init();
1198
1199
        if ($stmt->prepare($sqlQuery)) {
1200
            $OID = $this->BO->getOID();
1201
            $stmt->bind_param('i', $OID);
1202
1203
            $stmt->execute();
1204
1205
            $result = $this->bindResult($stmt);
1206
            if (isset($result[0])) {
1207
                $row = $result[0];
1208
            }
1209
1210
            $stmt->close();
1211
        } else {
1212
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
1213
            if (!$this->BO->checkTableExists()) {
1214
                $this->BO->makeTable();
1215
1216
                throw new RecordNotFoundException('Failed to get the version number, table did not exist so had to create!');
1217
            }
1218
1219
            return;
1220
        }
1221
1222
        if (!isset($row['version_num']) || $row['version_num'] < 1) {
1223
            self::$logger->debug('<<getVersion [0]');
1224
1225
            return 0;
1226
        } else {
1227
            $version_num = $row['version_num'];
1228
1229
            self::$logger->debug('<<getVersion ['.$version_num.']');
1230
1231
            return $version_num;
1232
        }
1233
    }
1234
1235
    /**
1236
     * (non-PHPdoc).
1237
     *
1238
     * @see Alpha\Model\ActiveRecordProviderInterface::makeTable()
1239
     */
1240
    public function makeTable()
1241
    {
1242
        self::$logger->debug('>>makeTable()');
1243
1244
        $sqlQuery = 'CREATE TABLE '.$this->BO->getTableName().' (OID INT(11) ZEROFILL NOT NULL AUTO_INCREMENT,';
1245
1246
        // get the class attributes
1247
        $reflection = new ReflectionClass(get_class($this->BO));
1248
        $properties = $reflection->getProperties();
1249
1250
        foreach ($properties as $propObj) {
1251
            $propName = $propObj->name;
1252
1253
            if (!in_array($propName, $this->BO->getTransientAttributes()) && $propName != 'OID') {
1254
                $prop = $this->BO->getPropObject($propName);
1255
1256
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1257
                    $sqlQuery .= "$propName INT(".$prop->getSize().') ZEROFILL NOT NULL,';
1258
                } elseif ($prop instanceof Integer) {
1259
                    $sqlQuery .= "$propName INT(".$prop->getSize().'),';
1260
                } elseif ($prop instanceof Double) {
1261
                    $sqlQuery .= "$propName DOUBLE(".$prop->getSize(true).'),';
1262
                } elseif ($prop instanceof SmallText) {
1263
                    $sqlQuery .= "$propName VARCHAR(".$prop->getSize().') CHARACTER SET utf8,';
1264
                } elseif ($prop instanceof Text) {
1265
                    $sqlQuery .= "$propName TEXT CHARACTER SET utf8,";
1266
                } elseif ($prop instanceof Boolean) {
1267
                    $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1268
                } elseif ($prop instanceof Date) {
1269
                    $sqlQuery .= "$propName DATE,";
1270
                } elseif ($prop instanceof Timestamp) {
1271
                    $sqlQuery .= "$propName DATETIME,";
1272
                } elseif ($prop instanceof Enum) {
1273
                    $sqlQuery .= "$propName ENUM(";
1274
                    $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getOptions() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Enum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1275
                    foreach ($enumVals as $val) {
1276
                        $sqlQuery .= "'".$val."',";
1277
                    }
1278
                    $sqlQuery = rtrim($sqlQuery, ',');
1279
                    $sqlQuery .= ') CHARACTER SET utf8,';
1280
                } elseif ($prop instanceof DEnum) {
1281
                    $tmp = new DEnum(get_class($this->BO).'::'.$propName);
0 ignored issues
show
Documentation introduced by
get_class($this->BO) . '::' . $propName is of type string, but the function expects a object<Alpha\Model\Type\SmallText>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
$tmp is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1282
                    $sqlQuery .= "$propName INT(11) ZEROFILL,";
1283
                } elseif ($prop instanceof Relation) {
1284
                    $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1285
                } else {
1286
                    $sqlQuery .= '';
1287
                }
1288
            }
1289
        }
1290
        if ($this->BO->isTableOverloaded()) {
1291
            $sqlQuery .= 'classname VARCHAR(100),';
1292
        }
1293
1294
        $sqlQuery .= 'PRIMARY KEY (OID)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1295
1296
        $this->BO->setLastQuery($sqlQuery);
1297
1298
        if (!$result = self::getConnection()->query($sqlQuery)) {
1299
            self::$logger->debug('<<makeTable');
1300
            throw new AlphaException('Failed to create the table ['.$this->BO->getTableName().'] for the class ['.get_class($this->BO).'], database error is ['.self::getConnection()->error.']');
1301
        }
1302
1303
        // check the table indexes if any additional ones required
1304
        $this->checkIndexes();
1305
1306
        if ($this->BO->getMaintainHistory()) {
1307
            $this->BO->makeHistoryTable();
1308
        }
1309
1310
        self::$logger->debug('<<makeTable');
1311
    }
1312
1313
    /**
1314
     * (non-PHPdoc).
1315
     *
1316
     * @see Alpha\Model\ActiveRecordProviderInterface::makeHistoryTable()
1317
     */
1318
    public function makeHistoryTable()
1319
    {
1320
        self::$logger->debug('>>makeHistoryTable()');
1321
1322
        $sqlQuery = 'CREATE TABLE '.$this->BO->getTableName().'_history (OID INT(11) ZEROFILL NOT NULL,';
1323
1324
        // get the class attributes
1325
        $reflection = new ReflectionClass(get_class($this->BO));
1326
        $properties = $reflection->getProperties();
1327
1328
        foreach ($properties as $propObj) {
1329
            $propName = $propObj->name;
1330
1331
            if (!in_array($propName, $this->BO->getTransientAttributes()) && $propName != 'OID') {
1332
                $propReflect = new ReflectionClass($this->BO->getPropObject($propName));
1333
                $propClass = $propReflect->getShortName();
1334
1335
                switch (mb_strtoupper($propClass)) {
1336
                    case 'INTEGER':
1337
                        // special properties for RelationLookup OIDs
1338
                        if ($this->BO instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1339
                            $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().') ZEROFILL NOT NULL,';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1340
                        } else {
1341
                            $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1342
                        }
1343
                    break;
1344
                    case 'DOUBLE':
1345
                        $sqlQuery .= "$propName DOUBLE(".$this->BO->getPropObject($propName)->getSize(true).'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1346
                    break;
1347
                    case 'SMALLTEXT':
1348
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1349
                    break;
1350
                    case 'TEXT':
1351
                        $sqlQuery .= "$propName TEXT,";
1352
                    break;
1353
                    case 'BOOLEAN':
1354
                        $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1355
                    break;
1356
                    case 'DATE':
1357
                        $sqlQuery .= "$propName DATE,";
1358
                    break;
1359
                    case 'TIMESTAMP':
1360
                        $sqlQuery .= "$propName DATETIME,";
1361
                    break;
1362
                    case 'ENUM':
1363
                        $sqlQuery .= "$propName ENUM(";
1364
1365
                        $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getOptions() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Enum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1366
1367
                        foreach ($enumVals as $val) {
1368
                            $sqlQuery .= "'".$val."',";
1369
                        }
1370
1371
                        $sqlQuery = rtrim($sqlQuery, ',');
1372
                        $sqlQuery .= '),';
1373
                    break;
1374
                    case 'DENUM':
1375
                        $tmp = new DEnum(get_class($this->BO).'::'.$propName);
0 ignored issues
show
Documentation introduced by
get_class($this->BO) . '::' . $propName is of type string, but the function expects a object<Alpha\Model\Type\SmallText>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
$tmp is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1376
                        $sqlQuery .= "$propName INT(11) ZEROFILL,";
1377
                    break;
1378
                    case 'RELATION':
1379
                        $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1380
                    break;
1381
                    default:
1382
                        $sqlQuery .= '';
1383
                    break;
1384
                }
1385
            }
1386
        }
1387
1388
        if ($this->BO->isTableOverloaded()) {
1389
            $sqlQuery .= 'classname VARCHAR(100),';
1390
        }
1391
1392
        $sqlQuery .= 'PRIMARY KEY (OID, version_num)) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1393
1394
        $this->BO->setLastQuery($sqlQuery);
1395
1396
        if (!$result = self::getConnection()->query($sqlQuery)) {
1397
            self::$logger->debug('<<makeHistoryTable');
1398
            throw new AlphaException('Failed to create the table ['.$this->BO->getTableName().'_history] for the class ['.get_class($this->BO).'], database error is ['.self::getConnection()->error.']');
1399
        }
1400
1401
        self::$logger->debug('<<makeHistoryTable');
1402
    }
1403
1404
    /**
1405
     * (non-PHPdoc).
1406
     *
1407
     * @see Alpha\Model\ActiveRecordProviderInterface::rebuildTable()
1408
     */
1409
    public function rebuildTable()
1410
    {
1411
        self::$logger->debug('>>rebuildTable()');
1412
1413
        $sqlQuery = 'DROP TABLE IF EXISTS '.$this->BO->getTableName().';';
1414
1415
        $this->BO->setLastQuery($sqlQuery);
1416
1417
        if (!$result = self::getConnection()->query($sqlQuery)) {
1418
            self::$logger->debug('<<rebuildTable');
1419
            throw new AlphaException('Failed to drop the table ['.$this->BO->getTableName().'] for the class ['.get_class($this->BO).'], database error is ['.self::getConnection()->error.']');
1420
        }
1421
1422
        $this->BO->makeTable();
1423
1424
        self::$logger->debug('<<rebuildTable');
1425
    }
1426
1427
    /**
1428
     * (non-PHPdoc).
1429
     *
1430
     * @see Alpha\Model\ActiveRecordProviderInterface::dropTable()
1431
     */
1432
    public function dropTable($tableName = null)
1433
    {
1434
        self::$logger->debug('>>dropTable()');
1435
1436
        if ($tableName === null) {
1437
            $tableName = $this->BO->getTableName();
1438
        }
1439
1440
        $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.';';
1441
1442
        $this->BO->setLastQuery($sqlQuery);
1443
1444
        if (!$result = self::getConnection()->query($sqlQuery)) {
1445
            self::$logger->debug('<<dropTable');
1446
            throw new AlphaException('Failed to drop the table ['.$tableName.'] for the class ['.get_class($this->BO).'], query is ['.$this->BO->getLastQuery().']');
1447
        }
1448
1449
        if ($this->BO->getMaintainHistory()) {
1450
            $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.'_history;';
1451
1452
            $this->BO->setLastQuery($sqlQuery);
1453
1454
            if (!$result = self::getConnection()->query($sqlQuery)) {
1455
                self::$logger->debug('<<dropTable');
1456
                throw new AlphaException('Failed to drop the table ['.$tableName.'_history] for the class ['.get_class($this->BO).'], query is ['.$this->BO->getLastQuery().']');
1457
            }
1458
        }
1459
1460
        self::$logger->debug('<<dropTable');
1461
    }
1462
1463
    /**
1464
     * (non-PHPdoc).
1465
     *
1466
     * @see Alpha\Model\ActiveRecordProviderInterface::addProperty()
1467
     */
1468
    public function addProperty($propName)
1469
    {
1470
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1471
1472
        $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD ';
1473
1474
        if ($this->isTableOverloaded() && $propName == 'classname') {
1475
            $sqlQuery .= 'classname VARCHAR(100)';
1476
        } else {
1477
            if (!in_array($propName, $this->BO->getDefaultAttributes()) && !in_array($propName, $this->BO->getTransientAttributes())) {
1478
                $reflection = new ReflectionClass($this->BO->getPropObject($propName));
1479
                $propClass = $reflection->getShortName();
1480
1481
                switch (mb_strtoupper($propClass)) {
1482
                    case 'INTEGER':
1483
                        $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1484
                    break;
1485
                    case 'DOUBLE':
1486
                        $sqlQuery .= "$propName DOUBLE(".$this->BO->getPropObject($propName)->getSize(true).')';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1487
                    break;
1488
                    case 'SMALLTEXT':
1489
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1490
                    break;
1491
                    case 'SEQUENCE':
1492
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getSize() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1493
                    break;
1494
                    case 'TEXT':
1495
                        $sqlQuery .= "$propName TEXT";
1496
                    break;
1497
                    case 'BOOLEAN':
1498
                        $sqlQuery .= "$propName CHAR(1) DEFAULT '0'";
1499
                    break;
1500
                    case 'DATE':
1501
                        $sqlQuery .= "$propName DATE";
1502
                    break;
1503
                    case 'TIMESTAMP':
1504
                        $sqlQuery .= "$propName DATETIME";
1505
                    break;
1506
                    case 'ENUM':
1507
                        $sqlQuery .= "$propName ENUM(";
1508
                        $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getOptions() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Enum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1509
                        foreach ($enumVals as $val) {
1510
                            $sqlQuery .= "'".$val."',";
1511
                        }
1512
                        $sqlQuery = rtrim($sqlQuery, ',');
1513
                        $sqlQuery .= ')';
1514
                    break;
1515
                    case 'DENUM':
1516
                        $tmp = new DEnum(get_class($this->BO).'::'.$propName);
0 ignored issues
show
Documentation introduced by
get_class($this->BO) . '::' . $propName is of type string, but the function expects a object<Alpha\Model\Type\SmallText>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1517
                        $tmp->save();
1518
                        $sqlQuery .= "$propName INT(11) ZEROFILL";
1519
                    break;
1520
                    case 'RELATION':
1521
                        $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED";
1522
                    break;
1523
                    default:
1524
                        $sqlQuery .= '';
1525
                    break;
1526
                }
1527
            }
1528
        }
1529
1530
        $this->BO->setLastQuery($sqlQuery);
1531
1532
        if (!$result = self::getConnection()->query($sqlQuery)) {
1533
            self::$logger->debug('<<addProperty');
1534
            throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->BO->getTableName().'], query is ['.$this->BO->getLastQuery().']');
1535
        } else {
1536
            self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->BO->getTableName().'] table for the class ['.get_class($this->BO).']');
1537
        }
1538
1539
        if ($this->BO->getMaintainHistory()) {
1540
            $sqlQuery = str_replace($this->BO->getTableName(), $this->BO->getTableName().'_history', $sqlQuery);
1541
1542
            if (!$result = self::getConnection()->query($sqlQuery)) {
1543
                self::$logger->debug('<<addProperty');
1544
                throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->BO->getTableName().'_history], query is ['.$this->BO->getLastQuery().']');
1545
            } else {
1546
                self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->BO->getTableName().'_history] table for the class ['.get_class($this->BO).']');
1547
            }
1548
        }
1549
1550
        self::$logger->debug('<<addProperty');
1551
    }
1552
1553
    /**
1554
     * (non-PHPdoc).
1555
     *
1556
     * @see Alpha\Model\ActiveRecordProviderInterface::getMAX()
1557
     */
1558
    public function getMAX()
1559
    {
1560
        self::$logger->debug('>>getMAX()');
1561
1562
        $sqlQuery = 'SELECT MAX(OID) AS max_OID FROM '.$this->BO->getTableName();
1563
1564
        $this->BO->setLastQuery($sqlQuery);
1565
1566
        try {
1567
            $result = $this->BO->query($sqlQuery);
1568
1569
            $row = $result[0];
1570
1571
            if (isset($row['max_OID'])) {
1572
                self::$logger->debug('<<getMAX ['.$row['max_OID'].']');
1573
1574
                return $row['max_OID'];
1575
            } else {
1576
                throw new AlphaException('Failed to get the MAX ID for the class ['.get_class($this->BO).'] from the table ['.$this->BO->getTableName().'], query is ['.$this->BO->getLastQuery().']');
1577
            }
1578
        } catch (\Exception $e) {
1579
            self::$logger->debug('<<getMAX');
1580
            throw new AlphaException($e->getMessage());
1581
        }
1582
    }
1583
1584
    /**
1585
     * (non-PHPdoc).
1586
     *
1587
     * @see Alpha\Model\ActiveRecordProviderInterface::getCount()
1588
     */
1589
    public function getCount($attributes = array(), $values = array())
1590
    {
1591
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1592
1593
        if ($this->BO->isTableOverloaded()) {
1594
            $whereClause = ' WHERE classname = \''.addslashes(get_class($this->BO)).'\' AND';
1595
        } else {
1596
            $whereClause = ' WHERE';
1597
        }
1598
1599
        $count = count($attributes);
1600
1601
        for ($i = 0; $i < $count; ++$i) {
1602
            $whereClause .= ' '.$attributes[$i].' = \''.$values[$i].'\' AND';
1603
            self::$logger->debug($whereClause);
1604
        }
1605
        // remove the last " AND"
1606
        $whereClause = mb_substr($whereClause, 0, -4);
1607
1608
        if ($whereClause != ' WHERE') {
1609
            $sqlQuery = 'SELECT COUNT(OID) AS class_count FROM '.$this->BO->getTableName().$whereClause;
1610
        } else {
1611
            $sqlQuery = 'SELECT COUNT(OID) AS class_count FROM '.$this->BO->getTableName();
1612
        }
1613
1614
        $this->BO->setLastQuery($sqlQuery);
1615
1616
        $result = self::getConnection()->query($sqlQuery);
1617
1618
        if ($result) {
1619
            $row = $result->fetch_array(MYSQLI_ASSOC);
1620
1621
            self::$logger->debug('<<getCount ['.$row['class_count'].']');
1622
1623
            return $row['class_count'];
1624
        } else {
1625
            self::$logger->debug('<<getCount');
1626
            throw new AlphaException('Failed to get the count for the class ['.get_class($this->BO).'] from the table ['.$this->BO->getTableName().'], query is ['.$this->BO->getLastQuery().']');
1627
        }
1628
    }
1629
1630
    /**
1631
     * (non-PHPdoc).
1632
     *
1633
     * @see Alpha\Model\ActiveRecordProviderInterface::getHistoryCount()
1634
     */
1635
    public function getHistoryCount()
1636
    {
1637
        self::$logger->debug('>>getHistoryCount()');
1638
1639
        if (!$this->BO->getMaintainHistory()) {
1640
            throw new AlphaException('getHistoryCount method called on a DAO where no history is maintained!');
1641
        }
1642
1643
        $sqlQuery = 'SELECT COUNT(OID) AS object_count FROM '.$this->BO->getTableName().'_history WHERE OID='.$this->BO->getOID();
1644
1645
        $this->BO->setLastQuery($sqlQuery);
1646
1647
        $result = self::getConnection()->query($sqlQuery);
1648
1649
        if ($result) {
1650
            $row = $result->fetch_array(MYSQLI_ASSOC);
1651
1652
            self::$logger->debug('<<getHistoryCount ['.$row['object_count'].']');
1653
1654
            return $row['object_count'];
1655
        } else {
1656
            self::$logger->debug('<<getHistoryCount');
1657
            throw new AlphaException('Failed to get the history count for the business object ['.$this->BO->getOID().'] from the table ['.$this->BO->getTableName().'_history], query is ['.$this->BO->getLastQuery().']');
1658
        }
1659
    }
1660
1661
    /**
1662
     * (non-PHPdoc).
1663
     *
1664
     * @see Alpha\Model\ActiveRecordProviderInterface::setEnumOptions()
1665
     * @since 1.1
1666
     */
1667
    public function setEnumOptions()
1668
    {
1669
        self::$logger->debug('>>setEnumOptions()');
1670
1671
        // get the class attributes
1672
        $reflection = new ReflectionClass(get_class($this->BO));
1673
        $properties = $reflection->getProperties();
1674
1675
        // flag for any database errors
1676
        $dbError = false;
1677
1678
        foreach ($properties as $propObj) {
1679
            $propName = $propObj->name;
1680
            if (!in_array($propName, $this->BO->getDefaultAttributes()) && !in_array($propName, $this->BO->getTransientAttributes())) {
1681
                $propClass = get_class($this->BO->getPropObject($propName));
1682
                if ($propClass == 'Enum') {
1683
                    $sqlQuery = 'SHOW COLUMNS FROM '.$this->BO->getTableName()." LIKE '$propName'";
1684
1685
                    $this->BO->setLastQuery($sqlQuery);
1686
1687
                    $result = self::getConnection()->query($sqlQuery);
1688
1689
                    if ($result) {
1690
                        $row = $result->fetch_array(MYSQLI_NUM);
1691
                        $options = explode("','", preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $row[1]));
1692
1693
                        $this->BO->getPropObject($propName)->setOptions($options);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method setOptions() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Enum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1694
                    } else {
1695
                        $dbError = true;
1696
                        break;
1697
                    }
1698
                }
1699
            }
1700
        }
1701
1702
        if (!$dbError) {
1703
            if (method_exists($this, 'after_setEnumOptions_callback')) {
1704
                $this->{'after_setEnumOptions_callback'}();
1705
            }
1706
        } else {
1707
            throw new AlphaException('Failed to load enum options correctly for object instance of class ['.get_class($this).']');
1708
        }
1709
        self::$logger->debug('<<setEnumOptions');
1710
    }
1711
1712
    /**
1713
     * (non-PHPdoc).
1714
     *
1715
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableExists()
1716
     */
1717
    public function checkTableExists($checkHistoryTable = false)
1718
    {
1719
        self::$logger->debug('>>checkTableExists(checkHistoryTable=['.$checkHistoryTable.'])');
1720
1721
        $tableExists = false;
1722
1723
        $sqlQuery = 'SHOW TABLES;';
1724
        $this->BO->setLastQuery($sqlQuery);
1725
1726
        $result = self::getConnection()->query($sqlQuery);
1727
1728
        if ($result) {
1729
            $tableName = ($checkHistoryTable ? $this->BO->getTableName().'_history' : $this->BO->getTableName());
1730
1731
            while ($row = $result->fetch_array(MYSQLI_NUM)) {
1732
                if (strtolower($row[0]) == mb_strtolower($tableName)) {
1733
                    $tableExists = true;
1734
                }
1735
            }
1736
1737
            self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1738
1739
            return $tableExists;
1740
        } else {
1741
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1742
            self::$logger->debug('<<checkTableExists [false]');
0 ignored issues
show
Unused Code introduced by
self::$logger->debug('<<...kTableExists [false]'); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1743
1744
            return false;
1745
        }
1746
    }
1747
1748
    /**
1749
     * (non-PHPdoc).
1750
     *
1751
     * @see Alpha\Model\ActiveRecordProviderInterface::checkBOTableExists()
1752
     */
1753
    public static function checkBOTableExists($BOClassName, $checkHistoryTable = false)
1754
    {
1755
        if (self::$logger == null) {
1756
            self::$logger = new Logger('ActiveRecordProviderMySQL');
1757
        }
1758
        self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'], checkHistoryTable=['.$checkHistoryTable.'])');
1759
1760
        if (!class_exists($BOClassName)) {
1761
            throw new IllegalArguementException('The classname provided ['.$checkHistoryTable.'] is not defined!');
1762
        }
1763
1764
        $tableName = $BOClassName::TABLE_NAME;
1765
1766
        if (empty($tableName)) {
1767
            $tableName = mb_substr($BOClassName, 0, mb_strpos($BOClassName, '_'));
1768
        }
1769
1770
        if ($checkHistoryTable) {
1771
            $tableName .= '_history';
1772
        }
1773
1774
        $tableExists = false;
1775
1776
        $sqlQuery = 'SHOW TABLES;';
1777
1778
        $result = self::getConnection()->query($sqlQuery);
1779
1780
        while ($row = $result->fetch_array(MYSQLI_NUM)) {
1781
            if ($row[0] == $tableName) {
1782
                $tableExists = true;
1783
            }
1784
        }
1785
1786
        if ($result) {
1787
            self::$logger->debug('<<checkBOTableExists ['.($tableExists ? 'true' : 'false').']');
1788
1789
            return $tableExists;
1790
        } else {
1791
            self::$logger->debug('<<checkBOTableExists');
1792
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1793
        }
1794
    }
1795
1796
    /**
1797
     * (non-PHPdoc).
1798
     *
1799
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableNeedsUpdate()
1800
     */
1801
    public function checkTableNeedsUpdate()
1802
    {
1803
        self::$logger->debug('>>checkTableNeedsUpdate()');
1804
1805
        $updateRequired = false;
1806
1807
        $matchCount = 0;
1808
1809
        $query = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
1810
        $result = self::getConnection()->query($query);
1811
        $this->BO->setLastQuery($query);
1812
1813
        // get the class attributes
1814
        $reflection = new ReflectionClass(get_class($this->BO));
1815
        $properties = $reflection->getProperties();
1816
1817
        foreach ($properties as $propObj) {
1818
            $propName = $propObj->name;
1819
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1820
                $foundMatch = false;
1821
1822
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1823
                    if ($propName == $row['Field']) {
1824
                        $foundMatch = true;
1825
                        break;
1826
                    }
1827
                }
1828
1829
                if (!$foundMatch) {
1830
                    --$matchCount;
1831
                }
1832
1833
                $result->data_seek(0);
1834
            }
1835
        }
1836
1837
        // check for the "classname" field in overloaded tables
1838
        if ($this->BO->isTableOverloaded()) {
1839
            $foundMatch = false;
1840
1841
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1842
                if ('classname' == $row['Field']) {
1843
                    $foundMatch = true;
1844
                    break;
1845
                }
1846
            }
1847
            if (!$foundMatch) {
1848
                --$matchCount;
1849
            }
1850
        }
1851
1852
        if ($matchCount != 0) {
1853
            $updateRequired = true;
1854
        }
1855
1856
        if ($result) {
1857
            // check the table indexes
1858
            try {
1859
                $this->checkIndexes();
1860
            } catch (AlphaException $ae) {
1861
                self::$logger->warn("Error while checking database indexes:\n\n".$ae->getMessage());
1862
            }
1863
1864
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1865
1866
            return $updateRequired;
1867
        } else {
1868
            self::$logger->debug('<<checkTableNeedsUpdate');
1869
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1870
        }
1871
    }
1872
1873
    /**
1874
     * (non-PHPdoc).
1875
     *
1876
     * @see Alpha\Model\ActiveRecordProviderInterface::findMissingFields()
1877
     */
1878
    public function findMissingFields()
1879
    {
1880
        self::$logger->debug('>>findMissingFields()');
1881
1882
        $missingFields = array();
1883
        $matchCount = 0;
1884
1885
        $sqlQuery = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
1886
1887
        $result = self::getConnection()->query($sqlQuery);
1888
1889
        $this->BO->setLastQuery($sqlQuery);
1890
1891
        // get the class attributes
1892
        $reflection = new ReflectionClass(get_class($this->BO));
1893
        $properties = $reflection->getProperties();
1894
1895
        foreach ($properties as $propObj) {
1896
            $propName = $propObj->name;
1897
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1898
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1899
                    if ($propName == $row['Field']) {
1900
                        ++$matchCount;
1901
                        break;
1902
                    }
1903
                }
1904
                $result->data_seek(0);
1905
            } else {
1906
                ++$matchCount;
1907
            }
1908
1909
            if ($matchCount == 0) {
1910
                array_push($missingFields, $propName);
1911
            } else {
1912
                $matchCount = 0;
1913
            }
1914
        }
1915
1916
        // check for the "classname" field in overloaded tables
1917
        if ($this->BO->isTableOverloaded()) {
1918
            $foundMatch = false;
1919
1920
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1921
                if ('classname' == $row['Field']) {
1922
                    $foundMatch = true;
1923
                    break;
1924
                }
1925
            }
1926
            if (!$foundMatch) {
1927
                array_push($missingFields, 'classname');
1928
            }
1929
        }
1930
1931
        if (!$result) {
1932
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1933
        }
1934
1935
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1936
1937
        return $missingFields;
1938
    }
1939
1940
    /**
1941
     * (non-PHPdoc).
1942
     *
1943
     * @see Alpha\Model\ActiveRecordProviderInterface::getIndexes()
1944
     */
1945
    public function getIndexes()
1946
    {
1947
        self::$logger->debug('>>getIndexes()');
1948
1949
        $query = 'SHOW INDEX FROM '.$this->BO->getTableName();
1950
1951
        $result = self::getConnection()->query($query);
1952
1953
        $this->BO->setLastQuery($query);
1954
1955
        $indexNames = array();
1956
1957
        if (!$result) {
1958
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1959
        } else {
1960
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1961
                array_push($indexNames, $row['Key_name']);
1962
            }
1963
        }
1964
1965
        self::$logger->debug('<<getIndexes');
1966
1967
        return $indexNames;
1968
    }
1969
1970
    /**
1971
     * Checks to see if all of the indexes are in place for the BO's table, creates those that are missing.
1972
     *
1973
     * @since 1.1
1974
     */
1975
    private function checkIndexes()
1976
    {
1977
        self::$logger->debug('>>checkIndexes()');
1978
1979
        $indexNames = $this->getIndexes();
1980
1981
        // process unique keys
1982
        foreach ($this->BO->getUniqueAttributes() as $prop) {
1983
            // check for composite indexes
1984
            if (mb_strpos($prop, '+')) {
1985
                $attributes = explode('+', $prop);
1986
1987
                $index_exists = false;
1988
                foreach ($indexNames as $index) {
1989
                    if ($attributes[0].'_'.$attributes[1].'_unq_idx' == $index) {
1990
                        $index_exists = true;
1991
                    }
1992
                    if (count($attributes) == 3) {
1993
                        if ($attributes[0].'_'.$attributes[1].'_'.$attributes[2].'_unq_idx' == $index) {
1994
                            $index_exists = true;
1995
                        }
1996
                    }
1997
                }
1998
1999
                if (!$index_exists) {
2000
                    if (count($attributes) == 3) {
2001
                        $this->BO->createUniqueIndex($attributes[0], $attributes[1], $attributes[2]);
2002
                    } else {
2003
                        $this->BO->createUniqueIndex($attributes[0], $attributes[1]);
2004
                    }
2005
                }
2006
            } else {
2007
                $index_exists = false;
2008
                foreach ($indexNames as $index) {
2009
                    if ($prop.'_unq_idx' == $index) {
2010
                        $index_exists = true;
2011
                    }
2012
                }
2013
2014
                if (!$index_exists) {
2015
                    $this->createUniqueIndex($prop);
2016
                }
2017
            }
2018
        }
2019
2020
        // process foreign-key indexes
2021
        // get the class attributes
2022
        $reflection = new ReflectionClass(get_class($this->BO));
2023
        $properties = $reflection->getProperties();
2024
2025
        foreach ($properties as $propObj) {
2026
            $propName = $propObj->name;
2027
            $prop = $this->BO->getPropObject($propName);
2028
            if ($prop instanceof Relation) {
2029
                if ($prop->getRelationType() == 'MANY-TO-ONE') {
2030
                    $indexExists = false;
2031
                    foreach ($indexNames as $index) {
2032
                        if ($this->BO->getTableName().'_'.$propName.'_fk_idx' == $index) {
2033
                            $indexExists = true;
2034
                        }
2035
                    }
2036
2037
                    if (!$indexExists) {
2038
                        $this->createForeignIndex($propName, $prop->getRelatedClass(), $prop->getRelatedClassField());
2039
                    }
2040
                }
2041
2042
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
2043
                    $lookup = $prop->getLookup();
2044
2045
                    if ($lookup != null) {
2046
                        try {
2047
                            $lookupIndexNames = $lookup->getIndexes();
2048
2049
                            // handle index check/creation on left side of Relation
2050
                            $indexExists = false;
2051
                            foreach ($lookupIndexNames as $index) {
2052
                                if ($lookup->getTableName().'_leftID_fk_idx' == $index) {
2053
                                    $indexExists = true;
2054
                                }
2055
                            }
2056
2057
                            if (!$indexExists) {
2058
                                $lookup->createForeignIndex('leftID', $prop->getRelatedClass('left'), 'OID');
2059
                            }
2060
2061
                            // handle index check/creation on right side of Relation
2062
                            $indexExists = false;
2063
                            foreach ($lookupIndexNames as $index) {
2064
                                if ($lookup->getTableName().'_rightID_fk_idx' == $index) {
2065
                                    $indexExists = true;
2066
                                }
2067
                            }
2068
2069
                            if (!$indexExists) {
2070
                                $lookup->createForeignIndex('rightID', $prop->getRelatedClass('right'), 'OID');
2071
                            }
2072
                        } catch (AlphaException $e) {
2073
                            self::$logger->error($e->getMessage());
2074
                        }
2075
                    }
2076
                }
2077
            }
2078
        }
2079
2080
        self::$logger->debug('<<checkIndexes');
2081
    }
2082
2083
    /**
2084
     * (non-PHPdoc).
2085
     *
2086
     * @see Alpha\Model\ActiveRecordProviderInterface::createForeignIndex()
2087
     */
2088
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
2089
    {
2090
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
2091
2092
        $relatedBO = new $relatedClass();
2093
        $tableName = $relatedBO->getTableName();
2094
2095
        $result = false;
2096
2097
        if (self::checkBOTableExists($relatedClass)) {
2098
            $sqlQuery = '';
2099
2100
            if ($attributeName == 'leftID') {
2101
                if ($indexName === null) {
2102
                    $indexName = $this->BO->getTableName().'_leftID_fk_idx';
2103
                }
2104
                $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD INDEX '.$indexName.' (leftID);';
2105
            }
2106
            if ($attributeName == 'rightID') {
2107
                if ($indexName === null) {
2108
                    $indexName = $this->BO->getTableName().'_rightID_fk_idx';
2109
                }
2110
                $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD INDEX '.$indexName.' (rightID);';
2111
            }
2112
2113
            if (!empty($sqlQuery)) {
2114
                $this->BO->setLastQuery($sqlQuery);
2115
2116
                $result = self::getConnection()->query($sqlQuery);
2117
2118
                if (!$result) {
2119
                    throw new FailedIndexCreateException('Failed to create an index on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
2120
                }
2121
            }
2122
2123
            if ($indexName === null) {
2124
                $indexName = $this->BO->getTableName().'_'.$attributeName.'_fk_idx';
2125
            }
2126
2127
            $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD FOREIGN KEY '.$indexName.' ('.$attributeName.') REFERENCES '.$tableName.' ('.$relatedClassAttribute.') ON DELETE SET NULL;';
2128
2129
            $this->BO->setLastQuery($sqlQuery);
2130
            $result = self::getConnection()->query($sqlQuery);
2131
        }
2132
2133
        if ($result) {
2134
            self::$logger->debug('Successfully created the foreign key index ['.$indexName.']');
2135
        } else {
2136
            throw new FailedIndexCreateException('Failed to create the index ['.$indexName.'] on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
2137
        }
2138
2139
        self::$logger->debug('<<createForeignIndex');
2140
    }
2141
2142
    /**
2143
     * (non-PHPdoc).
2144
     *
2145
     * @see Alpha\Model\ActiveRecordProviderInterface::createUniqueIndex()
2146
     */
2147
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
2148
    {
2149
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
2150
2151
        $sqlQuery = '';
2152
2153
        if ($attribute2Name != '' && $attribute3Name != '') {
2154
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_'.$attribute3Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.','.$attribute2Name.','.$attribute3Name.');';
2155
        }
2156
2157
        if ($attribute2Name != '' && $attribute3Name == '') {
2158
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.','.$attribute2Name.');';
2159
        }
2160
2161
        if ($attribute2Name == '' && $attribute3Name == '') {
2162
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.');';
2163
        }
2164
2165
        $this->BO->setLastQuery($sqlQuery);
2166
2167
        $result = self::getConnection()->query($sqlQuery);
2168
2169
        if ($result) {
2170
            self::$logger->debug('Successfully created the unique index on ['.$this->BO->getTableName().']');
2171
        } else {
2172
            throw new FailedIndexCreateException('Failed to create the unique index on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.']');
2173
        }
2174
2175
        self::$logger->debug('<<createUniqueIndex');
2176
    }
2177
2178
    /**
2179
     * (non-PHPdoc).
2180
     *
2181
     * @see Alpha\Model\ActiveRecordProviderInterface::reload()
2182
     */
2183
    public function reload()
2184
    {
2185
        self::$logger->debug('>>reload()');
2186
2187
        if (!$this->BO->isTransient()) {
2188
            $this->BO->load($this->BO->getOID());
2189
        } else {
2190
            throw new AlphaException('Cannot reload transient object from database!');
2191
        }
2192
        self::$logger->debug('<<reload');
2193
    }
2194
2195
    /**
2196
     * (non-PHPdoc).
2197
     *
2198
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordExists()
2199
     */
2200
    public function checkRecordExists($OID)
2201
    {
2202
        self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
2203
2204
        $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' WHERE OID = ?;';
2205
2206
        $this->BO->setLastQuery($sqlQuery);
2207
2208
        $stmt = self::getConnection()->stmt_init();
2209
2210
        if ($stmt->prepare($sqlQuery)) {
2211
            $stmt->bind_param('i', $OID);
2212
2213
            $stmt->execute();
2214
2215
            $result = $this->bindResult($stmt);
2216
2217
            $stmt->close();
2218
2219
            if (is_array($result)) {
2220
                if (count($result) > 0) {
2221
                    self::$logger->debug('<<checkRecordExists [true]');
2222
2223
                    return true;
2224
                } else {
2225
                    self::$logger->debug('<<checkRecordExists [false]');
2226
2227
                    return false;
2228
                }
2229
            } else {
2230
                self::$logger->debug('<<checkRecordExists');
2231
                throw new AlphaException('Failed to check for the record ['.$OID.'] on the class ['.get_class($this->BO).'] from the table ['.$this->BO->getTableName().'], query is ['.$this->BO->getLastQuery().']');
2232
            }
2233
        } else {
2234
            self::$logger->debug('<<checkRecordExists');
2235
            throw new AlphaException('Failed to check for the record ['.$OID.'] on the class ['.get_class($this->BO).'] from the table ['.$this->BO->getTableName().'], query is ['.$this->BO->getLastQuery().']');
2236
        }
2237
    }
2238
2239
    /**
2240
     * (non-PHPdoc).
2241
     *
2242
     * @see Alpha\Model\ActiveRecordProviderInterface::isTableOverloaded()
2243
     */
2244
    public function isTableOverloaded()
2245
    {
2246
        self::$logger->debug('>>isTableOverloaded()');
2247
2248
        $reflection = new ReflectionClass($this->BO);
2249
        $classname = $reflection->getShortName();
2250
        $tablename = ucfirst($this->BO->getTableName());
2251
2252
        // use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
2253
        $implementedInterfaces = $reflection->getInterfaces();
2254
2255
        foreach ($implementedInterfaces as $interface) {
2256
            if ($interface->name == 'Alpha\Model\Type\TypeInterface') {
2257
                self::$logger->debug('<<isTableOverloaded [false]');
2258
2259
                return false;
2260
            }
2261
        }
2262
2263
        if ($classname != $tablename) {
2264
            // loop over all BOs to see if there is one using the same table as this BO
2265
2266
            $BOclasses = ActiveRecord::getBOClassNames();
2267
2268
            foreach ($BOclasses as $BOclassName) {
2269
                $reflection = new ReflectionClass($BOclassName);
2270
                $classname = $reflection->getShortName();
2271
                if ($tablename == $classname) {
2272
                    self::$logger->debug('<<isTableOverloaded [true]');
2273
2274
                    return true;
2275
                }
2276
            }
2277
2278
            self::$logger->debug('<<isTableOverloaded');
2279
            throw new BadTableNameException('The table name ['.$tablename.'] for the class ['.$classname.'] is invalid as it does not match a BO definition in the system!');
2280
        } else {
2281
            // check to see if there is already a "classname" column in the database for this BO
2282
2283
            $query = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
2284
2285
            $result = self::getConnection()->query($query);
2286
2287
            if ($result) {
2288
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
2289
                    if ('classname' == $row['Field']) {
2290
                        self::$logger->debug('<<isTableOverloaded [true]');
2291
2292
                        return true;
2293
                    }
2294
                }
2295
            } else {
2296
                self::$logger->warn('Error during show columns ['.self::getConnection()->error.']');
2297
            }
2298
2299
            self::$logger->debug('<<isTableOverloaded [false]');
2300
2301
            return false;
2302
        }
2303
    }
2304
2305
    /**
2306
     * (non-PHPdoc).
2307
     *
2308
     * @see Alpha\Model\ActiveRecordProviderInterface::begin()
2309
     */
2310
    public static function begin()
2311
    {
2312
        if (self::$logger == null) {
2313
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2314
        }
2315
        self::$logger->debug('>>begin()');
2316
2317
        if (!self::getConnection()->autocommit(false)) {
2318
            throw new AlphaException('Error beginning a new transaction, error is ['.self::getConnection()->error.']');
2319
        }
2320
2321
        self::$logger->debug('<<begin');
2322
    }
2323
2324
    /**
2325
     * (non-PHPdoc).
2326
     *
2327
     * @see Alpha\Model\ActiveRecordProviderInterface::commit()
2328
     */
2329
    public static function commit()
2330
    {
2331
        if (self::$logger == null) {
2332
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2333
        }
2334
        self::$logger->debug('>>commit()');
2335
2336
        if (!self::getConnection()->commit()) {
2337
            throw new FailedSaveException('Error commiting a transaction, error is ['.self::getConnection()->error.']');
2338
        }
2339
2340
        self::$logger->debug('<<commit');
2341
    }
2342
2343
    /**
2344
     * (non-PHPdoc).
2345
     *
2346
     * @see Alpha\Model\ActiveRecordProviderInterface::rollback()
2347
     */
2348
    public static function rollback()
2349
    {
2350
        if (self::$logger == null) {
2351
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2352
        }
2353
        self::$logger->debug('>>rollback()');
2354
2355
        if (!self::getConnection()->rollback()) {
2356
            throw new AlphaException('Error rolling back a transaction, error is ['.self::getConnection()->error.']');
2357
        }
2358
2359
        self::$logger->debug('<<rollback');
2360
    }
2361
2362
    /**
2363
     * (non-PHPdoc).
2364
     *
2365
     * @see Alpha\Model\ActiveRecordProviderInterface::setBO()
2366
     */
2367
    public function setBO($BO)
2368
    {
2369
        $this->BO = $BO;
2370
    }
2371
2372
    /**
2373
     * Dynamically binds all of the attributes for the current BO to the supplied prepared statement
2374
     * parameters.  If arrays of attribute names and values are provided, only those will be bound to
2375
     * the supplied statement.
2376
     *
2377
     * @param mysqli_stmt $stmt The SQL statement to bind to.
2378
     * @param array Optional array of BO attributes.
2379
     * @param array Optional array of BO values.
2380
     *
2381
     * @return mysqli_stmt
2382
     *
2383
     * @since 1.1
2384
     */
2385
    private function bindParams($stmt, $attributes = array(), $values = array())
2386
    {
2387
        self::$logger->debug('>>bindParams(stmt=['.var_export($stmt, true).'])');
2388
2389
        $bindingsTypes = '';
2390
        $params = array();
2391
2392
        // here we are only binding the supplied attributes
2393
        if (count($attributes) > 0 && count($attributes) == count($values)) {
2394
            $count = count($values);
2395
2396
            for ($i = 0; $i < $count; ++$i) {
2397
                if (Validator::isInteger($values[$i])) {
2398
                    $bindingsTypes .= 'i';
2399
                } else {
2400
                    $bindingsTypes .= 's';
2401
                }
2402
                array_push($params, $values[$i]);
2403
            }
2404
2405
            if ($this->BO->isTableOverloaded()) {
2406
                $bindingsTypes .= 's';
2407
                array_push($params, get_class($this->BO));
2408
            }
2409
        } else { // bind all attributes on the business object
2410
2411
            // get the class attributes
2412
            $reflection = new ReflectionClass(get_class($this->BO));
2413
            $properties = $reflection->getProperties();
2414
2415
            foreach ($properties as $propObj) {
2416
                $propName = $propObj->name;
2417
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
2418
                    // Skip the OID, database auto number takes care of this.
2419
                    if ($propName != 'OID' && $propName != 'version_num') {
2420
                        if ($this->BO->getPropObject($propName) instanceof Integer) {
2421
                            $bindingsTypes .= 'i';
2422
                        } else {
2423
                            $bindingsTypes .= 's';
2424
                        }
2425
                        array_push($params, $this->BO->get($propName));
2426
                    }
2427
2428
                    if ($propName == 'version_num') {
2429
                        $temp = $this->BO->getVersionNumber()->getValue();
2430
                        $this->BO->set('version_num', $temp + 1);
2431
                        $bindingsTypes .= 'i';
2432
                        array_push($params, $this->BO->getVersionNumber()->getValue());
2433
                    }
2434
                }
2435
            }
2436
2437
            if ($this->BO->isTableOverloaded()) {
2438
                $bindingsTypes .= 's';
2439
                array_push($params, get_class($this->BO));
2440
            }
2441
2442
            // the OID may be on the WHERE clause for UPDATEs and DELETEs
2443
            if (!$this->BO->isTransient()) {
2444
                $bindingsTypes .= 'i';
2445
                array_push($params, $this->BO->getOID());
2446
            }
2447
        }
2448
2449
        self::$logger->debug('bindingsTypes=['.$bindingsTypes.'], count: ['.mb_strlen($bindingsTypes).']');
2450
        self::$logger->debug('params ['.var_export($params, true).']');
2451
2452
        if ($params != null) {
2453
            $bind_names[] = $bindingsTypes;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$bind_names was never initialized. Although not strictly required by PHP, it is generally a good practice to add $bind_names = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2454
2455
            $count = count($params);
2456
2457
            for ($i = 0; $i < $count; ++$i) {
2458
                $bind_name = 'bind'.$i;
2459
                $$bind_name = $params[$i];
2460
                $bind_names[] = &$$bind_name;
2461
            }
2462
2463
            call_user_func_array(array($stmt, 'bind_param'), $bind_names);
2464
        }
2465
2466
        self::$logger->debug('<<bindParams ['.var_export($stmt, true).']');
2467
2468
        return $stmt;
2469
    }
2470
2471
    /**
2472
     * Dynamically binds the result of the supplied prepared statement to a 2d array, where each element in the array is another array
2473
     * representing a database row.
2474
     *
2475
     * @param \mysqli_stmt $stmt
2476
     *
2477
     * @return array A 2D array containing the query result.
2478
     *
2479
     * @since 1.1
2480
     */
2481
    private function bindResult($stmt)
2482
    {
2483
        $result = array();
2484
2485
        $metadata = $stmt->result_metadata();
2486
        $fields = $metadata->fetch_fields();
2487
2488
        while (true) {
2489
            $pointers = array();
2490
            $row = array();
2491
2492
            $pointers[] = $stmt;
2493
            foreach ($fields as $field) {
2494
                $fieldname = $field->name;
2495
                $pointers[] = &$row[$fieldname];
2496
            }
2497
2498
            call_user_func_array('mysqli_stmt_bind_result', $pointers);
2499
2500
            if (!$stmt->fetch()) {
2501
                break;
2502
            }
2503
2504
            $result[] = $row;
2505
        }
2506
2507
        $metadata->free();
2508
2509
        return $result;
2510
    }
2511
2512
    /**
2513
     * Parses a MySQL error for the value that violated a unique constraint.
2514
     *
2515
     * @param string $error The MySQL error string.
2516
     *
2517
     * @since 1.1
2518
     */
2519
    private function findOffendingValue($error)
2520
    {
2521
        self::$logger->debug('>>findOffendingValue(error=['.$error.'])');
2522
2523
        $singleQuote1 = mb_strpos($error, "'");
2524
        $singleQuote2 = mb_strrpos($error, "'");
2525
2526
        $value = mb_substr($error, $singleQuote1, ($singleQuote2 - $singleQuote1) + 1);
2527
        self::$logger->debug('<<findOffendingValue ['.$value.'])');
2528
2529
        return $value;
2530
    }
2531
2532
    /**
2533
     * (non-PHPdoc).
2534
     *
2535
     * @see Alpha\Model\ActiveRecordProviderInterface::checkDatabaseExists()
2536
     */
2537
    public static function checkDatabaseExists()
2538
    {
2539
        $config = ConfigProvider::getInstance();
2540
2541
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2542
2543
        $result = $connection->query('SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \''.$config->get('db.name').'\'');
2544
2545
        if (count($result) > 0) {
2546
            return true;
2547
        } else {
2548
            return false;
2549
        }
2550
    }
2551
2552
    /**
2553
     * (non-PHPdoc).
2554
     *
2555
     * @see Alpha\Model\ActiveRecordProviderInterface::createDatabase()
2556
     */
2557
    public static function createDatabase()
2558
    {
2559
        $config = ConfigProvider::getInstance();
2560
2561
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2562
2563
        $connection->query('CREATE DATABASE '.$config->get('db.name'));
2564
    }
2565
2566
    /**
2567
     * (non-PHPdoc).
2568
     *
2569
     * @see Alpha\Model\ActiveRecordProviderInterface::dropDatabase()
2570
     */
2571
    public static function dropDatabase()
2572
    {
2573
        $config = ConfigProvider::getInstance();
2574
2575
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2576
2577
        $connection->query('DROP DATABASE '.$config->get('db.name'));
2578
    }
2579
}
2580