Completed
Push — develop ( c0835d...cf9d2c )
by John
03:19
created

ActiveRecordProviderMySQL::loadAll()   C

Complexity

Conditions 9
Paths 25

Size

Total Lines 53
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 6.8963
c 0
b 0
f 0
cc 9
eloc 33
nc 25
nop 5

How to fix   Long Method   

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

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
671
672
            $stmt->close();
673
        } else {
674
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
675
676
            if (!$this->BO->checkTableExists()) {
677
                $this->BO->makeTable();
678
679
                throw new RecordNotFoundException('Failed to load objects by attributes ['.var_export($attributes, true).'] and values ['.
680
                    var_export($values, true).'], table did not exist so had to create!');
681
            }
682
683
            self::$logger->debug('<<loadAllByAttributes []');
684
685
            return array();
686
        }
687
688
        // now build an array of objects to be returned
689
        $objects = array();
690
        $count = 0;
691
        $RecordClass = get_class($this->BO);
692
693
        foreach ($result as $row) {
694
            try {
695
                $argsCount = count($constructorArgs);
696
697
                if ($argsCount < 1) {
698
                    $obj = new $RecordClass();
699
                } else {
700
                    switch ($argsCount) {
701
                        case 1:
702
                            $obj = new $RecordClass($constructorArgs[0]);
703
                        break;
704
                        case 2:
705
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
706
                        break;
707
                        case 3:
708
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
709
                        break;
710
                        case 4:
711
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
712
                        break;
713
                        case 5:
714
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
715
                        break;
716
                        default:
717
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
718
                    }
719
                }
720
721
                $obj->load($row['OID']);
722
                $objects[$count] = $obj;
723
                ++$count;
724
            } catch (ResourceNotAllowedException $e) {
725
                // the resource not allowed will be absent from the list
726
            }
727
        }
728
729
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
730
731
        return $objects;
732
    }
733
734
    /**
735
     * (non-PHPdoc).
736
     *
737
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByDayUpdated()
738
     */
739
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'OID', $order = 'ASC', $ignoreClassType = false)
740
    {
741
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
742
743
        if ($start != 0 && $limit != 0) {
744
            $limit = ' LIMIT '.$start.', '.$limit.';';
745
        } else {
746
            $limit = ';';
747
        }
748
749
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
750
            $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;
751
        } else {
752
            $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;
753
        }
754
755
        $this->BO->setLastQuery($sqlQuery);
756
757
        if (!$result = self::getConnection()->query($sqlQuery)) {
758
            self::$logger->debug('<<loadAllByDayUpdated []');
759
            throw new RecordNotFoundException('Failed to load object OIDs, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
760
        }
761
762
        // now build an array of objects to be returned
763
        $objects = array();
764
        $count = 0;
765
        $RecordClass = get_class($this->BO);
766
767
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
768
            $obj = new $RecordClass();
769
            $obj->load($row['OID']);
770
            $objects[$count] = $obj;
771
            ++$count;
772
        }
773
774
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
775
776
        return $objects;
777
    }
778
779
    /**
780
     * (non-PHPdoc).
781
     *
782
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllFieldValuesByAttribute()
783
     */
784
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
785
    {
786
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
787
788
        if (!$ignoreClassType && $this->BO->isTableOverloaded()) {
789
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->BO->getTableName()." WHERE $attribute = '$value' AND classname = '".addslashes(get_class($this->BO))."' ORDER BY OID ".$order.';';
790
        } else {
791
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->BO->getTableName()." WHERE $attribute = '$value' ORDER BY OID ".$order.';';
792
        }
793
794
        $this->BO->setLastQuery($sqlQuery);
795
796
        self::$logger->debug('lastQuery ['.$sqlQuery.']');
797
798
        if (!$result = self::getConnection()->query($sqlQuery)) {
799
            self::$logger->debug('<<loadAllFieldValuesByAttribute []');
800
            throw new RecordNotFoundException('Failed to load field ['.$returnAttribute.'] values, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
801
        }
802
803
        // now build an array of attribute values to be returned
804
        $values = array();
805
        $count = 0;
806
807
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
808
            $values[$count] = $row[$returnAttribute];
809
            ++$count;
810
        }
811
812
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
813
814
        return $values;
815
    }
816
817
    /**
818
     * (non-PHPdoc).
819
     *
820
     * @see Alpha\Model\ActiveRecordProviderInterface::save()
821
     */
822
    public function save()
823
    {
824
        self::$logger->debug('>>save()');
825
826
        $config = ConfigProvider::getInstance();
827
        $sessionProvider = $config->get('session.provider.name');
828
        $session = SessionProviderFactory::getInstance($sessionProvider);
829
830
        // get the class attributes
831
        $reflection = new ReflectionClass(get_class($this->BO));
832
        $properties = $reflection->getProperties();
833
834
        if ($this->BO->getVersion() != $this->BO->getVersionNumber()->getValue()) {
835
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
836
        }
837
838
        // set the "updated by" fields, we can only set the user id if someone is logged in
839
        if ($session->get('currentUser') != null) {
840
            $this->BO->set('updated_by', $session->get('currentUser')->getOID());
841
        }
842
843
        $this->BO->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
844
845
        // check to see if it is a transient object that needs to be inserted
846
        if ($this->BO->isTransient()) {
847
            $savedFieldsCount = 0;
848
            $sqlQuery = 'INSERT INTO '.$this->BO->getTableName().' (';
849
850
            foreach ($properties as $propObj) {
851
                $propName = $propObj->name;
852
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
853
                    // Skip the OID, database auto number takes care of this.
854
                    if ($propName != 'OID' && $propName != 'version_num') {
855
                        $sqlQuery .= "$propName,";
856
                        ++$savedFieldsCount;
857
                    }
858
859
                    if ($propName == 'version_num') {
860
                        $sqlQuery .= 'version_num,';
861
                        ++$savedFieldsCount;
862
                    }
863
                }
864
            }
865
866
            if ($this->BO->isTableOverloaded()) {
867
                $sqlQuery .= 'classname,';
868
            }
869
870
            $sqlQuery = rtrim($sqlQuery, ',');
871
872
            $sqlQuery .= ') VALUES (';
873
874
            for ($i = 0; $i < $savedFieldsCount; ++$i) {
875
                $sqlQuery .= '?,';
876
            }
877
878
            if ($this->BO->isTableOverloaded()) {
879
                $sqlQuery .= '?,';
880
            }
881
882
            $sqlQuery = rtrim($sqlQuery, ',').')';
883
884
            $this->BO->setLastQuery($sqlQuery);
885
            self::$logger->debug('Query ['.$sqlQuery.']');
886
887
            $stmt = self::getConnection()->stmt_init();
888
889
            if ($stmt->prepare($sqlQuery)) {
890
                $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...
891
                $stmt->execute();
892
            } else {
893
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
894
            }
895
        } else {
896
            // assume that it is a persistent object that needs to be updated
897
            $savedFieldsCount = 0;
898
            $sqlQuery = 'UPDATE '.$this->BO->getTableName().' SET ';
899
900
            foreach ($properties as $propObj) {
901
                $propName = $propObj->name;
902
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
903
                    // Skip the OID, database auto number takes care of this.
904
                    if ($propName != 'OID' && $propName != 'version_num') {
905
                        $sqlQuery .= "$propName = ?,";
906
                        ++$savedFieldsCount;
907
                    }
908
909
                    if ($propName == 'version_num') {
910
                        $sqlQuery .= 'version_num = ?,';
911
                        ++$savedFieldsCount;
912
                    }
913
                }
914
            }
915
916
            if ($this->BO->isTableOverloaded()) {
917
                $sqlQuery .= 'classname = ?,';
918
            }
919
920
            $sqlQuery = rtrim($sqlQuery, ',');
921
922
            $sqlQuery .= ' WHERE OID=?;';
923
924
            $this->BO->setLastQuery($sqlQuery);
925
            $stmt = self::getConnection()->stmt_init();
926
927
            if ($stmt->prepare($sqlQuery)) {
928
                $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...
929
                $stmt->execute();
930
            } else {
931
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
932
            }
933
        }
934
935
        if ($stmt != null && $stmt->error == '') {
936
            // populate the updated OID in case we just done an insert
937
            if ($this->BO->isTransient()) {
938
                $this->BO->setOID(self::getConnection()->insert_id);
939
            }
940
941
            try {
942
                foreach ($properties as $propObj) {
943
                    $propName = $propObj->name;
944
945
                    if ($this->BO->getPropObject($propName) instanceof Relation) {
946
                        $prop = $this->BO->getPropObject($propName);
947
948
                        // handle the saving of MANY-TO-MANY relation values
949
                        if ($prop->getRelationType() == 'MANY-TO-MANY' && count($prop->getRelatedOIDs()) > 0) {
950
                            try {
951
                                try {
952
                                    // check to see if the rel is on this class
953
                                    $side = $prop->getSide(get_class($this->BO));
954
                                } catch (IllegalArguementException $iae) {
955
                                    $side = $prop->getSide(get_parent_class($this->BO));
956
                                }
957
958
                                $lookUp = $prop->getLookup();
959
960
                                // first delete all of the old RelationLookup objects for this rel
961
                                try {
962
                                    if ($side == 'left') {
963
                                        $lookUp->deleteAllByAttribute('leftID', $this->BO->getOID());
964
                                    } else {
965
                                        $lookUp->deleteAllByAttribute('rightID', $this->BO->getOID());
966
                                    }
967
                                } catch (\Exception $e) {
968
                                    throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
969
                                }
970
971
                                $OIDs = $prop->getRelatedOIDs();
972
973
                                if (isset($OIDs) && !empty($OIDs[0])) {
974
                                    // now for each posted OID, create a new RelationLookup record and save
975
                                    foreach ($OIDs as $oid) {
976
                                        $newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
977
                                        if ($side == 'left') {
978
                                            $newLookUp->set('leftID', $this->BO->getOID());
979
                                            $newLookUp->set('rightID', $oid);
980
                                        } else {
981
                                            $newLookUp->set('rightID', $this->BO->getOID());
982
                                            $newLookUp->set('leftID', $oid);
983
                                        }
984
                                        $newLookUp->save();
985
                                    }
986
                                }
987
                            } catch (\Exception $e) {
988
                                throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
989
                            }
990
                        }
991
992
                        // handle the saving of ONE-TO-MANY relation values
993
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
994
                            $prop->setValue($this->BO->getOID());
995
                        }
996
                    }
997
                }
998
            } catch (\Exception $e) {
999
                throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
1000
            }
1001
1002
            $stmt->close();
1003
        } else {
1004
            // there has been an error, so decrement the version number back
1005
            $temp = $this->BO->getVersionNumber()->getValue();
1006
            $this->BO->set('version_num', $temp - 1);
1007
1008
            // check for unique violations
1009
            if (self::getConnection()->errno == '1062') {
1010
                throw new ValidationException('Failed to save, the value '.$this->findOffendingValue(self::getConnection()->error).' is already in use!');
1011
            } else {
1012
                throw new FailedSaveException('Failed to save object, MySql error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
1013
            }
1014
        }
1015
1016
        if ($this->BO->getMaintainHistory()) {
1017
            $this->BO->saveHistory();
1018
        }
1019
    }
1020
1021
    /**
1022
     * (non-PHPdoc).
1023
     *
1024
     * @see Alpha\Model\ActiveRecordProviderInterface::saveAttribute()
1025
     */
1026
    public function saveAttribute($attribute, $value)
1027
    {
1028
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
1029
1030
        // assume that it is a persistent object that needs to be updated
1031
        $sqlQuery = 'UPDATE '.$this->BO->getTableName().' SET '.$attribute.'=?, version_num = ? WHERE OID=?;';
1032
1033
        $this->BO->setLastQuery($sqlQuery);
1034
        $stmt = self::getConnection()->stmt_init();
1035
1036
        $newVersionNumber = $this->BO->getVersionNumber()->getValue() + 1;
1037
1038
        if ($stmt->prepare($sqlQuery)) {
1039
            if ($this->BO->getPropObject($attribute) instanceof Integer) {
1040
                $bindingsType = 'i';
1041
            } else {
1042
                $bindingsType = 's';
1043
            }
1044
            $OID = $this->BO->getOID();
1045
            $stmt->bind_param($bindingsType.'ii', $value, $newVersionNumber, $OID);
1046
            self::$logger->debug('Binding params ['.$bindingsType.'i, '.$value.', '.$OID.']');
1047
            $stmt->execute();
1048
        } else {
1049
            throw new FailedSaveException('Failed to save attribute, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1050
        }
1051
1052
        $stmt->close();
1053
1054
        $this->BO->set($attribute, $value);
1055
        $this->BO->set('version_num', $newVersionNumber);
1056
1057
        if ($this->BO->getMaintainHistory()) {
1058
            $this->BO->saveHistory();
1059
        }
1060
1061
        self::$logger->debug('<<saveAttribute');
1062
    }
1063
1064
    /**
1065
     * (non-PHPdoc).
1066
     *
1067
     * @see Alpha\Model\ActiveRecordProviderInterface::saveHistory()
1068
     */
1069
    public function saveHistory()
1070
    {
1071
        self::$logger->debug('>>saveHistory()');
1072
1073
        // get the class attributes
1074
        $reflection = new ReflectionClass(get_class($this->BO));
1075
        $properties = $reflection->getProperties();
1076
1077
        $savedFieldsCount = 0;
1078
        $attributeNames = array();
1079
        $attributeValues = array();
1080
1081
        $sqlQuery = 'INSERT INTO '.$this->BO->getTableName().'_history (';
1082
1083
        foreach ($properties as $propObj) {
1084
            $propName = $propObj->name;
1085
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1086
                $sqlQuery .= "$propName,";
1087
                $attributeNames[] = $propName;
1088
                $attributeValues[] = $this->BO->get($propName);
1089
                ++$savedFieldsCount;
1090
            }
1091
        }
1092
1093
        if ($this->BO->isTableOverloaded()) {
1094
            $sqlQuery .= 'classname,';
1095
        }
1096
1097
        $sqlQuery = rtrim($sqlQuery, ',');
1098
1099
        $sqlQuery .= ') VALUES (';
1100
1101
        for ($i = 0; $i < $savedFieldsCount; ++$i) {
1102
            $sqlQuery .= '?,';
1103
        }
1104
1105
        if ($this->BO->isTableOverloaded()) {
1106
            $sqlQuery .= '?,';
1107
        }
1108
1109
        $sqlQuery = rtrim($sqlQuery, ',').')';
1110
1111
        $this->BO->setLastQuery($sqlQuery);
1112
        self::$logger->debug('Query ['.$sqlQuery.']');
1113
1114
        $stmt = self::getConnection()->stmt_init();
1115
1116
        if ($stmt->prepare($sqlQuery)) {
1117
            $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...
1118
            $stmt->execute();
1119
        } else {
1120
            throw new FailedSaveException('Failed to save object history, error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1121
        }
1122
    }
1123
1124
    /**
1125
     * (non-PHPdoc).
1126
     *
1127
     * @see Alpha\Model\ActiveRecordProviderInterface::delete()
1128
     */
1129
    public function delete()
1130
    {
1131
        self::$logger->debug('>>delete()');
1132
1133
        $sqlQuery = 'DELETE FROM '.$this->BO->getTableName().' WHERE OID = ?;';
1134
1135
        $this->BO->setLastQuery($sqlQuery);
1136
1137
        $stmt = self::getConnection()->stmt_init();
1138
1139
        if ($stmt->prepare($sqlQuery)) {
1140
            $OID = $this->BO->getOID();
1141
            $stmt->bind_param('i', $OID);
1142
            $stmt->execute();
1143
            self::$logger->debug('Deleted the object ['.$this->BO->getOID().'] of class ['.get_class($this->BO).']');
1144
        } else {
1145
            throw new FailedDeleteException('Failed to delete object ['.$this->BO->getOID().'], error is ['.$stmt->error.'], query ['.$this->BO->getLastQuery().']');
1146
        }
1147
1148
        $stmt->close();
1149
1150
        self::$logger->debug('<<delete');
1151
    }
1152
1153
    /**
1154
     * (non-PHPdoc).
1155
     *
1156
     * @see Alpha\Model\ActiveRecordProviderInterface::getVersion()
1157
     */
1158
    public function getVersion()
1159
    {
1160
        self::$logger->debug('>>getVersion()');
1161
1162
        $sqlQuery = 'SELECT version_num FROM '.$this->BO->getTableName().' WHERE OID = ?;';
1163
        $this->BO->setLastQuery($sqlQuery);
1164
1165
        $stmt = self::getConnection()->stmt_init();
1166
1167
        if ($stmt->prepare($sqlQuery)) {
1168
            $OID = $this->BO->getOID();
1169
            $stmt->bind_param('i', $OID);
1170
1171
            $stmt->execute();
1172
1173
            $result = $this->bindResult($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...
1174
            if (isset($result[0])) {
1175
                $row = $result[0];
1176
            }
1177
1178
            $stmt->close();
1179
        } else {
1180
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
1181
            if (!$this->BO->checkTableExists()) {
1182
                $this->BO->makeTable();
1183
1184
                throw new RecordNotFoundException('Failed to get the version number, table did not exist so had to create!');
1185
            }
1186
1187
            return;
1188
        }
1189
1190
        if (!isset($row['version_num']) || $row['version_num'] < 1) {
1191
            self::$logger->debug('<<getVersion [0]');
1192
1193
            return 0;
1194
        } else {
1195
            $version_num = $row['version_num'];
1196
1197
            self::$logger->debug('<<getVersion ['.$version_num.']');
1198
1199
            return $version_num;
1200
        }
1201
    }
1202
1203
    /**
1204
     * (non-PHPdoc).
1205
     *
1206
     * @see Alpha\Model\ActiveRecordProviderInterface::makeTable()
1207
     */
1208
    public function makeTable()
1209
    {
1210
        self::$logger->debug('>>makeTable()');
1211
1212
        $sqlQuery = 'CREATE TABLE '.$this->BO->getTableName().' (OID INT(11) ZEROFILL NOT NULL AUTO_INCREMENT,';
1213
1214
        // get the class attributes
1215
        $reflection = new ReflectionClass(get_class($this->BO));
1216
        $properties = $reflection->getProperties();
1217
1218
        foreach ($properties as $propObj) {
1219
            $propName = $propObj->name;
1220
1221
            if (!in_array($propName, $this->BO->getTransientAttributes()) && $propName != 'OID') {
1222
                $propReflect = new ReflectionClass($this->BO->getPropObject($propName));
1223
                $propClass = $propReflect->getShortName();
1224
1225
                switch (mb_strtoupper($propClass)) {
1226
                    case 'INTEGER':
1227
                        // special properties for RelationLookup OIDs
1228
                        if ($this->BO instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1229
                            $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 concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1230
                        } else {
1231
                            $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1232
                        }
1233
                    break;
1234
                    case 'DOUBLE':
1235
                        $sqlQuery .= "$propName DOUBLE(".$this->BO->getPropObject($propName)->getSize(true).'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1236
                    break;
1237
                    case 'SMALLTEXT':
1238
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().') CHARACTER SET utf8,';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1239
                    break;
1240
                    case 'TEXT':
1241
                        $sqlQuery .= "$propName TEXT CHARACTER SET utf8,";
1242
                    break;
1243
                    case 'BOOLEAN':
1244
                        $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1245
                    break;
1246
                    case 'DATE':
1247
                        $sqlQuery .= "$propName DATE,";
1248
                    break;
1249
                    case 'TIMESTAMP':
1250
                        $sqlQuery .= "$propName DATETIME,";
1251
                    break;
1252
                    case 'ENUM':
1253
                        $sqlQuery .= "$propName ENUM(";
1254
                        $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getOptions() does only exist in the following implementations of said interface: Alpha\Model\Type\DEnum, Alpha\Model\Type\Enum.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1255
                        foreach ($enumVals as $val) {
1256
                            $sqlQuery .= "'".$val."',";
1257
                        }
1258
                        $sqlQuery = rtrim($sqlQuery, ',');
1259
                        $sqlQuery .= ') CHARACTER SET utf8,';
1260
                    break;
1261
                    case 'DENUM':
1262
                        $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...
1263
                        $sqlQuery .= "$propName INT(11) ZEROFILL,";
1264
                    break;
1265
                    case 'RELATION':
1266
                        $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1267
                    break;
1268
                    default:
1269
                        $sqlQuery .= '';
1270
                    break;
1271
                }
1272
            }
1273
        }
1274
        if ($this->BO->isTableOverloaded()) {
1275
            $sqlQuery .= 'classname VARCHAR(100),';
1276
        }
1277
1278
        $sqlQuery .= 'PRIMARY KEY (OID)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1279
1280
        $this->BO->setLastQuery($sqlQuery);
1281
1282
        if (!$result = self::getConnection()->query($sqlQuery)) {
1283
            self::$logger->debug('<<makeTable');
1284
            throw new AlphaException('Failed to create the table ['.$this->BO->getTableName().'] for the class ['.get_class($this->BO).'], database error is ['.self::getConnection()->error.']');
1285
        }
1286
1287
        // check the table indexes if any additional ones required
1288
        $this->checkIndexes();
1289
1290
        if ($this->BO->getMaintainHistory()) {
1291
            $this->BO->makeHistoryTable();
1292
        }
1293
1294
        self::$logger->debug('<<makeTable');
1295
    }
1296
1297
    /**
1298
     * (non-PHPdoc).
1299
     *
1300
     * @see Alpha\Model\ActiveRecordProviderInterface::makeHistoryTable()
1301
     */
1302
    public function makeHistoryTable()
1303
    {
1304
        self::$logger->debug('>>makeHistoryTable()');
1305
1306
        $sqlQuery = 'CREATE TABLE '.$this->BO->getTableName().'_history (OID INT(11) ZEROFILL NOT NULL,';
1307
1308
        // get the class attributes
1309
        $reflection = new ReflectionClass(get_class($this->BO));
1310
        $properties = $reflection->getProperties();
1311
1312
        foreach ($properties as $propObj) {
1313
            $propName = $propObj->name;
1314
1315
            if (!in_array($propName, $this->BO->getTransientAttributes()) && $propName != 'OID') {
1316
                $propReflect = new ReflectionClass($this->BO->getPropObject($propName));
1317
                $propClass = $propReflect->getShortName();
1318
1319
                switch (mb_strtoupper($propClass)) {
1320
                    case 'INTEGER':
1321
                        // special properties for RelationLookup OIDs
1322
                        if ($this->BO instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1323
                            $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 concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1324
                        } else {
1325
                            $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1326
                        }
1327
                    break;
1328
                    case 'DOUBLE':
1329
                        $sqlQuery .= "$propName DOUBLE(".$this->BO->getPropObject($propName)->getSize(true).'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1330
                    break;
1331
                    case 'SMALLTEXT':
1332
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().'),';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1333
                    break;
1334
                    case 'TEXT':
1335
                        $sqlQuery .= "$propName TEXT,";
1336
                    break;
1337
                    case 'BOOLEAN':
1338
                        $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1339
                    break;
1340
                    case 'DATE':
1341
                        $sqlQuery .= "$propName DATE,";
1342
                    break;
1343
                    case 'TIMESTAMP':
1344
                        $sqlQuery .= "$propName DATETIME,";
1345
                    break;
1346
                    case 'ENUM':
1347
                        $sqlQuery .= "$propName ENUM(";
1348
1349
                        $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getOptions() does only exist in the following implementations of said interface: Alpha\Model\Type\DEnum, Alpha\Model\Type\Enum.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1350
1351
                        foreach ($enumVals as $val) {
1352
                            $sqlQuery .= "'".$val."',";
1353
                        }
1354
1355
                        $sqlQuery = rtrim($sqlQuery, ',');
1356
                        $sqlQuery .= '),';
1357
                    break;
1358
                    case 'DENUM':
1359
                        $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...
1360
                        $sqlQuery .= "$propName INT(11) ZEROFILL,";
1361
                    break;
1362
                    case 'RELATION':
1363
                        $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1364
                    break;
1365
                    default:
1366
                        $sqlQuery .= '';
1367
                    break;
1368
                }
1369
            }
1370
        }
1371
1372
        if ($this->BO->isTableOverloaded()) {
1373
            $sqlQuery .= 'classname VARCHAR(100),';
1374
        }
1375
1376
        $sqlQuery .= 'PRIMARY KEY (OID, version_num)) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1377
1378
        $this->BO->setLastQuery($sqlQuery);
1379
1380
        if (!$result = self::getConnection()->query($sqlQuery)) {
1381
            self::$logger->debug('<<makeHistoryTable');
1382
            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.']');
1383
        }
1384
1385
        self::$logger->debug('<<makeHistoryTable');
1386
    }
1387
1388
    /**
1389
     * (non-PHPdoc).
1390
     *
1391
     * @see Alpha\Model\ActiveRecordProviderInterface::rebuildTable()
1392
     */
1393
    public function rebuildTable()
1394
    {
1395
        self::$logger->debug('>>rebuildTable()');
1396
1397
        $sqlQuery = 'DROP TABLE IF EXISTS '.$this->BO->getTableName().';';
1398
1399
        $this->BO->setLastQuery($sqlQuery);
1400
1401
        if (!$result = self::getConnection()->query($sqlQuery)) {
1402
            self::$logger->debug('<<rebuildTable');
1403
            throw new AlphaException('Failed to drop the table ['.$this->BO->getTableName().'] for the class ['.get_class($this->BO).'], database error is ['.self::getConnection()->error.']');
1404
        }
1405
1406
        $this->BO->makeTable();
1407
1408
        self::$logger->debug('<<rebuildTable');
1409
    }
1410
1411
    /**
1412
     * (non-PHPdoc).
1413
     *
1414
     * @see Alpha\Model\ActiveRecordProviderInterface::dropTable()
1415
     */
1416
    public function dropTable($tableName = null)
1417
    {
1418
        self::$logger->debug('>>dropTable()');
1419
1420
        if ($tableName === null) {
1421
            $tableName = $this->BO->getTableName();
1422
        }
1423
1424
        $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.';';
1425
1426
        $this->BO->setLastQuery($sqlQuery);
1427
1428
        if (!$result = self::getConnection()->query($sqlQuery)) {
1429
            self::$logger->debug('<<dropTable');
1430
            throw new AlphaException('Failed to drop the table ['.$tableName.'] for the class ['.get_class($this->BO).'], query is ['.$this->BO->getLastQuery().']');
1431
        }
1432
1433
        if ($this->BO->getMaintainHistory()) {
1434
            $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.'_history;';
1435
1436
            $this->BO->setLastQuery($sqlQuery);
1437
1438
            if (!$result = self::getConnection()->query($sqlQuery)) {
1439
                self::$logger->debug('<<dropTable');
1440
                throw new AlphaException('Failed to drop the table ['.$tableName.'_history] for the class ['.get_class($this->BO).'], query is ['.$this->BO->getLastQuery().']');
1441
            }
1442
        }
1443
1444
        self::$logger->debug('<<dropTable');
1445
    }
1446
1447
    /**
1448
     * (non-PHPdoc).
1449
     *
1450
     * @see Alpha\Model\ActiveRecordProviderInterface::addProperty()
1451
     */
1452
    public function addProperty($propName)
1453
    {
1454
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1455
1456
        $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD ';
1457
1458
        if ($this->isTableOverloaded() && $propName == 'classname') {
1459
            $sqlQuery .= 'classname VARCHAR(100)';
1460
        } else {
1461
            if (!in_array($propName, $this->BO->getDefaultAttributes()) && !in_array($propName, $this->BO->getTransientAttributes())) {
1462
                $reflection = new ReflectionClass($this->BO->getPropObject($propName));
1463
                $propClass = $reflection->getShortName();
1464
1465
                switch (mb_strtoupper($propClass)) {
1466
                    case 'INTEGER':
1467
                        $sqlQuery .= "$propName INT(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1468
                    break;
1469
                    case 'DOUBLE':
1470
                        $sqlQuery .= "$propName DOUBLE(".$this->BO->getPropObject($propName)->getSize(true).')';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1471
                    break;
1472
                    case 'SMALLTEXT':
1473
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1474
                    break;
1475
                    case 'SEQUENCE':
1476
                        $sqlQuery .= "$propName VARCHAR(".$this->BO->getPropObject($propName)->getSize().')';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getSize() does only exist in the following implementations of said interface: Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\Sequence, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1477
                    break;
1478
                    case 'TEXT':
1479
                        $sqlQuery .= "$propName TEXT";
1480
                    break;
1481
                    case 'BOOLEAN':
1482
                        $sqlQuery .= "$propName CHAR(1) DEFAULT '0'";
1483
                    break;
1484
                    case 'DATE':
1485
                        $sqlQuery .= "$propName DATE";
1486
                    break;
1487
                    case 'TIMESTAMP':
1488
                        $sqlQuery .= "$propName DATETIME";
1489
                    break;
1490
                    case 'ENUM':
1491
                        $sqlQuery .= "$propName ENUM(";
1492
                        $enumVals = $this->BO->getPropObject($propName)->getOptions();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Alpha\Model\Type\TypeInterface as the method getOptions() does only exist in the following implementations of said interface: Alpha\Model\Type\DEnum, Alpha\Model\Type\Enum.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

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

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1678
                    } else {
1679
                        $dbError = true;
1680
                        break;
1681
                    }
1682
                }
1683
            }
1684
        }
1685
1686
        if (!$dbError) {
1687
            if (method_exists($this, 'after_setEnumOptions_callback')) {
1688
                $this->{'after_setEnumOptions_callback'}();
1689
            }
1690
        } else {
1691
            throw new AlphaException('Failed to load enum options correctly for object instance of class ['.get_class($this).']');
1692
        }
1693
        self::$logger->debug('<<setEnumOptions');
1694
    }
1695
1696
    /**
1697
     * (non-PHPdoc).
1698
     *
1699
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableExists()
1700
     */
1701
    public function checkTableExists($checkHistoryTable = false)
1702
    {
1703
        self::$logger->debug('>>checkTableExists(checkHistoryTable=['.$checkHistoryTable.'])');
1704
1705
        $tableExists = false;
1706
1707
        $sqlQuery = 'SHOW TABLES;';
1708
        $this->BO->setLastQuery($sqlQuery);
1709
1710
        $result = self::getConnection()->query($sqlQuery);
1711
1712
        if ($result) {
1713
            $tableName = ($checkHistoryTable ? $this->BO->getTableName().'_history' : $this->BO->getTableName());
1714
1715
            while ($row = $result->fetch_array(MYSQLI_NUM)) {
1716
                if (strtolower($row[0]) == mb_strtolower($tableName)) {
1717
                    $tableExists = true;
1718
                }
1719
            }
1720
1721
            self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1722
1723
            return $tableExists;
1724
        } else {
1725
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1726
            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...
1727
1728
            return false;
1729
        }
1730
    }
1731
1732
    /**
1733
     * (non-PHPdoc).
1734
     *
1735
     * @see Alpha\Model\ActiveRecordProviderInterface::checkBOTableExists()
1736
     */
1737
    public static function checkBOTableExists($BOClassName, $checkHistoryTable = false)
1738
    {
1739
        if (self::$logger == null) {
1740
            self::$logger = new Logger('ActiveRecordProviderMySQL');
1741
        }
1742
        self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'], checkHistoryTable=['.$checkHistoryTable.'])');
1743
1744
        if (!class_exists($BOClassName)) {
1745
            throw new IllegalArguementException('The classname provided ['.$checkHistoryTable.'] is not defined!');
1746
        }
1747
1748
        $tableName = $BOClassName::TABLE_NAME;
1749
1750
        if (empty($tableName)) {
1751
            $tableName = mb_substr($BOClassName, 0, mb_strpos($BOClassName, '_'));
1752
        }
1753
1754
        if ($checkHistoryTable) {
1755
            $tableName .= '_history';
1756
        }
1757
1758
        $tableExists = false;
1759
1760
        $sqlQuery = 'SHOW TABLES;';
1761
1762
        $result = self::getConnection()->query($sqlQuery);
1763
1764
        while ($row = $result->fetch_array(MYSQLI_NUM)) {
1765
            if ($row[0] == $tableName) {
1766
                $tableExists = true;
1767
            }
1768
        }
1769
1770
        if ($result) {
1771
            self::$logger->debug('<<checkBOTableExists ['.($tableExists ? 'true' : 'false').']');
1772
1773
            return $tableExists;
1774
        } else {
1775
            self::$logger->debug('<<checkBOTableExists');
1776
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1777
        }
1778
    }
1779
1780
    /**
1781
     * (non-PHPdoc).
1782
     *
1783
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableNeedsUpdate()
1784
     */
1785
    public function checkTableNeedsUpdate()
1786
    {
1787
        self::$logger->debug('>>checkTableNeedsUpdate()');
1788
1789
        $updateRequired = false;
1790
1791
        $matchCount = 0;
1792
1793
        $query = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
1794
        $result = self::getConnection()->query($query);
1795
        $this->BO->setLastQuery($query);
1796
1797
        // get the class attributes
1798
        $reflection = new ReflectionClass(get_class($this->BO));
1799
        $properties = $reflection->getProperties();
1800
1801
        foreach ($properties as $propObj) {
1802
            $propName = $propObj->name;
1803
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1804
                $foundMatch = false;
1805
1806
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1807
                    if ($propName == $row['Field']) {
1808
                        $foundMatch = true;
1809
                        break;
1810
                    }
1811
                }
1812
1813
                if (!$foundMatch) {
1814
                    --$matchCount;
1815
                }
1816
1817
                $result->data_seek(0);
1818
            }
1819
        }
1820
1821
        // check for the "classname" field in overloaded tables
1822
        if ($this->BO->isTableOverloaded()) {
1823
            $foundMatch = false;
1824
1825
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1826
                if ('classname' == $row['Field']) {
1827
                    $foundMatch = true;
1828
                    break;
1829
                }
1830
            }
1831
            if (!$foundMatch) {
1832
                --$matchCount;
1833
            }
1834
        }
1835
1836
        if ($matchCount != 0) {
1837
            $updateRequired = true;
1838
        }
1839
1840
        if ($result) {
1841
            // check the table indexes
1842
            try {
1843
                $this->checkIndexes();
1844
            } catch (AlphaException $ae) {
1845
                self::$logger->warn("Error while checking database indexes:\n\n".$ae->getMessage());
1846
            }
1847
1848
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1849
1850
            return $updateRequired;
1851
        } else {
1852
            self::$logger->debug('<<checkTableNeedsUpdate');
1853
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1854
        }
1855
    }
1856
1857
    /**
1858
     * (non-PHPdoc).
1859
     *
1860
     * @see Alpha\Model\ActiveRecordProviderInterface::findMissingFields()
1861
     */
1862
    public function findMissingFields()
1863
    {
1864
        self::$logger->debug('>>findMissingFields()');
1865
1866
        $missingFields = array();
1867
        $matchCount = 0;
1868
1869
        $sqlQuery = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
1870
1871
        $result = self::getConnection()->query($sqlQuery);
1872
1873
        $this->BO->setLastQuery($sqlQuery);
1874
1875
        // get the class attributes
1876
        $reflection = new ReflectionClass(get_class($this->BO));
1877
        $properties = $reflection->getProperties();
1878
1879
        foreach ($properties as $propObj) {
1880
            $propName = $propObj->name;
1881
            if (!in_array($propName, $this->BO->getTransientAttributes())) {
1882
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1883
                    if ($propName == $row['Field']) {
1884
                        ++$matchCount;
1885
                        break;
1886
                    }
1887
                }
1888
                $result->data_seek(0);
1889
            } else {
1890
                ++$matchCount;
1891
            }
1892
1893
            if ($matchCount == 0) {
1894
                array_push($missingFields, $propName);
1895
            } else {
1896
                $matchCount = 0;
1897
            }
1898
        }
1899
1900
        // check for the "classname" field in overloaded tables
1901
        if ($this->BO->isTableOverloaded()) {
1902
            $foundMatch = false;
1903
1904
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1905
                if ('classname' == $row['Field']) {
1906
                    $foundMatch = true;
1907
                    break;
1908
                }
1909
            }
1910
            if (!$foundMatch) {
1911
                array_push($missingFields, 'classname');
1912
            }
1913
        }
1914
1915
        if (!$result) {
1916
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1917
        }
1918
1919
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1920
1921
        return $missingFields;
1922
    }
1923
1924
    /**
1925
     * (non-PHPdoc).
1926
     *
1927
     * @see Alpha\Model\ActiveRecordProviderInterface::getIndexes()
1928
     */
1929
    public function getIndexes()
1930
    {
1931
        self::$logger->debug('>>getIndexes()');
1932
1933
        $query = 'SHOW INDEX FROM '.$this->BO->getTableName();
1934
1935
        $result = self::getConnection()->query($query);
1936
1937
        $this->BO->setLastQuery($query);
1938
1939
        $indexNames = array();
1940
1941
        if (!$result) {
1942
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1943
        } else {
1944
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1945
                array_push($indexNames, $row['Key_name']);
1946
            }
1947
        }
1948
1949
        self::$logger->debug('<<getIndexes');
1950
1951
        return $indexNames;
1952
    }
1953
1954
    /**
1955
     * Checks to see if all of the indexes are in place for the BO's table, creates those that are missing.
1956
     *
1957
     * @since 1.1
1958
     */
1959
    private function checkIndexes()
1960
    {
1961
        self::$logger->debug('>>checkIndexes()');
1962
1963
        $indexNames = $this->getIndexes();
1964
1965
        // process unique keys
1966
        foreach ($this->BO->getUniqueAttributes() as $prop) {
1967
            // check for composite indexes
1968
            if (mb_strpos($prop, '+')) {
1969
                $attributes = explode('+', $prop);
1970
1971
                $index_exists = false;
1972
                foreach ($indexNames as $index) {
1973
                    if ($attributes[0].'_'.$attributes[1].'_unq_idx' == $index) {
1974
                        $index_exists = true;
1975
                    }
1976
                    if (count($attributes) == 3) {
1977
                        if ($attributes[0].'_'.$attributes[1].'_'.$attributes[2].'_unq_idx' == $index) {
1978
                            $index_exists = true;
1979
                        }
1980
                    }
1981
                }
1982
1983
                if (!$index_exists) {
1984
                    if (count($attributes) == 3) {
1985
                        $this->BO->createUniqueIndex($attributes[0], $attributes[1], $attributes[2]);
1986
                    } else {
1987
                        $this->BO->createUniqueIndex($attributes[0], $attributes[1]);
1988
                    }
1989
                }
1990
            } else {
1991
                $index_exists = false;
1992
                foreach ($indexNames as $index) {
1993
                    if ($prop.'_unq_idx' == $index) {
1994
                        $index_exists = true;
1995
                    }
1996
                }
1997
1998
                if (!$index_exists) {
1999
                    $this->createUniqueIndex($prop);
2000
                }
2001
            }
2002
        }
2003
2004
        // process foreign-key indexes
2005
        // get the class attributes
2006
        $reflection = new ReflectionClass(get_class($this->BO));
2007
        $properties = $reflection->getProperties();
2008
2009
        foreach ($properties as $propObj) {
2010
            $propName = $propObj->name;
2011
            $prop = $this->BO->getPropObject($propName);
2012
            if ($prop instanceof Relation) {
2013
                if ($prop->getRelationType() == 'MANY-TO-ONE') {
2014
                    $indexExists = false;
2015
                    foreach ($indexNames as $index) {
2016
                        if ($this->BO->getTableName().'_'.$propName.'_fk_idx' == $index) {
2017
                            $indexExists = true;
2018
                        }
2019
                    }
2020
2021
                    if (!$indexExists) {
2022
                        $this->createForeignIndex($propName, $prop->getRelatedClass(), $prop->getRelatedClassField());
2023
                    }
2024
                }
2025
2026
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
2027
                    $lookup = $prop->getLookup();
2028
2029
                    if ($lookup != null) {
2030
                        try {
2031
                            $lookupIndexNames = $lookup->getIndexes();
2032
2033
                            // handle index check/creation on left side of Relation
2034
                            $indexExists = false;
2035
                            foreach ($lookupIndexNames as $index) {
2036
                                if ($lookup->getTableName().'_leftID_fk_idx' == $index) {
2037
                                    $indexExists = true;
2038
                                }
2039
                            }
2040
2041
                            if (!$indexExists) {
2042
                                $lookup->createForeignIndex('leftID', $prop->getRelatedClass('left'), 'OID');
2043
                            }
2044
2045
                            // handle index check/creation on right side of Relation
2046
                            $indexExists = false;
2047
                            foreach ($lookupIndexNames as $index) {
2048
                                if ($lookup->getTableName().'_rightID_fk_idx' == $index) {
2049
                                    $indexExists = true;
2050
                                }
2051
                            }
2052
2053
                            if (!$indexExists) {
2054
                                $lookup->createForeignIndex('rightID', $prop->getRelatedClass('right'), 'OID');
2055
                            }
2056
                        } catch (AlphaException $e) {
2057
                            self::$logger->error($e->getMessage());
2058
                        }
2059
                    }
2060
                }
2061
            }
2062
        }
2063
2064
        self::$logger->debug('<<checkIndexes');
2065
    }
2066
2067
    /**
2068
     * (non-PHPdoc).
2069
     *
2070
     * @see Alpha\Model\ActiveRecordProviderInterface::createForeignIndex()
2071
     */
2072
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
2073
    {
2074
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
2075
2076
        $relatedBO = new $relatedClass();
2077
        $tableName = $relatedBO->getTableName();
2078
2079
        $result = false;
2080
2081
        if (self::checkBOTableExists($relatedClass)) {
2082
            $sqlQuery = '';
2083
2084
            if ($attributeName == 'leftID') {
2085
                if ($indexName === null) {
2086
                    $indexName = $this->BO->getTableName().'_leftID_fk_idx';
2087
                }
2088
                $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD INDEX '.$indexName.' (leftID);';
2089
            }
2090
            if ($attributeName == 'rightID') {
2091
                if ($indexName === null) {
2092
                    $indexName = $this->BO->getTableName().'_rightID_fk_idx';
2093
                }
2094
                $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD INDEX '.$indexName.' (rightID);';
2095
            }
2096
2097
            if (!empty($sqlQuery)) {
2098
                $this->BO->setLastQuery($sqlQuery);
2099
2100
                $result = self::getConnection()->query($sqlQuery);
2101
2102
                if (!$result) {
2103
                    throw new FailedIndexCreateException('Failed to create an index on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
2104
                }
2105
            }
2106
2107
            if ($indexName === null) {
2108
                $indexName = $this->BO->getTableName().'_'.$attributeName.'_fk_idx';
2109
            }
2110
2111
            $sqlQuery = 'ALTER TABLE '.$this->BO->getTableName().' ADD FOREIGN KEY '.$indexName.' ('.$attributeName.') REFERENCES '.$tableName.' ('.$relatedClassAttribute.') ON DELETE SET NULL;';
2112
2113
            $this->BO->setLastQuery($sqlQuery);
2114
            $result = self::getConnection()->query($sqlQuery);
2115
        }
2116
2117
        if ($result) {
2118
            self::$logger->debug('Successfully created the foreign key index ['.$indexName.']');
2119
        } else {
2120
            throw new FailedIndexCreateException('Failed to create the index ['.$indexName.'] on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->BO->getLastQuery().']');
2121
        }
2122
2123
        self::$logger->debug('<<createForeignIndex');
2124
    }
2125
2126
    /**
2127
     * (non-PHPdoc).
2128
     *
2129
     * @see Alpha\Model\ActiveRecordProviderInterface::createUniqueIndex()
2130
     */
2131
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
2132
    {
2133
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
2134
2135
        $sqlQuery = '';
2136
2137
        if ($attribute2Name != '' && $attribute3Name != '') {
2138
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_'.$attribute3Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.','.$attribute2Name.','.$attribute3Name.');';
2139
        }
2140
2141
        if ($attribute2Name != '' && $attribute3Name == '') {
2142
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.','.$attribute2Name.');';
2143
        }
2144
2145
        if ($attribute2Name == '' && $attribute3Name == '') {
2146
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_unq_idx ON '.$this->BO->getTableName().' ('.$attribute1Name.');';
2147
        }
2148
2149
        $this->BO->setLastQuery($sqlQuery);
2150
2151
        $result = self::getConnection()->query($sqlQuery);
2152
2153
        if ($result) {
2154
            self::$logger->debug('Successfully created the unique index on ['.$this->BO->getTableName().']');
2155
        } else {
2156
            throw new FailedIndexCreateException('Failed to create the unique index on ['.$this->BO->getTableName().'], error is ['.self::getConnection()->error.']');
2157
        }
2158
2159
        self::$logger->debug('<<createUniqueIndex');
2160
    }
2161
2162
    /**
2163
     * (non-PHPdoc).
2164
     *
2165
     * @see Alpha\Model\ActiveRecordProviderInterface::reload()
2166
     */
2167
    public function reload()
2168
    {
2169
        self::$logger->debug('>>reload()');
2170
2171
        if (!$this->BO->isTransient()) {
2172
            $this->BO->load($this->getOID());
0 ignored issues
show
Bug introduced by
The method getOID() does not seem to exist on object<Alpha\Model\ActiveRecordProviderMySQL>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2173
        } else {
2174
            throw new AlphaException('Cannot reload transient object from database!');
2175
        }
2176
        self::$logger->debug('<<reload');
2177
    }
2178
2179
    /**
2180
     * (non-PHPdoc).
2181
     *
2182
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordExists()
2183
     */
2184
    public function checkRecordExists($OID)
2185
    {
2186
        self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
2187
2188
        $sqlQuery = 'SELECT OID FROM '.$this->BO->getTableName().' WHERE OID = ?;';
2189
2190
        $this->BO->setLastQuery($sqlQuery);
2191
2192
        $stmt = self::getConnection()->stmt_init();
2193
2194
        if ($stmt->prepare($sqlQuery)) {
2195
            $stmt->bind_param('i', $OID);
2196
2197
            $stmt->execute();
2198
2199
            $result = $this->bindResult($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...
2200
2201
            $stmt->close();
2202
2203
            if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2204
                if (count($result) > 0) {
2205
                    self::$logger->debug('<<checkRecordExists [true]');
2206
2207
                    return true;
2208
                } else {
2209
                    self::$logger->debug('<<checkRecordExists [false]');
2210
2211
                    return false;
2212
                }
2213
            } else {
2214
                self::$logger->debug('<<checkRecordExists');
2215
                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().']');
2216
            }
2217
        } else {
2218
            self::$logger->debug('<<checkRecordExists');
2219
            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().']');
2220
        }
2221
    }
2222
2223
    /**
2224
     * (non-PHPdoc).
2225
     *
2226
     * @see Alpha\Model\ActiveRecordProviderInterface::isTableOverloaded()
2227
     */
2228
    public function isTableOverloaded()
2229
    {
2230
        self::$logger->debug('>>isTableOverloaded()');
2231
2232
        $reflection = new ReflectionClass($this->BO);
2233
        $classname = $reflection->getShortName();
2234
        $tablename = ucfirst($this->BO->getTableName());
2235
2236
        // use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
2237
        $implementedInterfaces = $reflection->getInterfaces();
2238
2239
        foreach ($implementedInterfaces as $interface) {
2240
            if ($interface->name == 'Alpha\Model\Type\TypeInterface') {
2241
                self::$logger->debug('<<isTableOverloaded [false]');
2242
2243
                return false;
2244
            }
2245
        }
2246
2247
        if ($classname != $tablename) {
2248
            // loop over all BOs to see if there is one using the same table as this BO
2249
2250
            $BOclasses = ActiveRecord::getBOClassNames();
2251
2252
            foreach ($BOclasses as $BOclassName) {
2253
                $reflection = new ReflectionClass($BOclassName);
2254
                $classname = $reflection->getShortName();
2255
                if ($tablename == $classname) {
2256
                    self::$logger->debug('<<isTableOverloaded [true]');
2257
2258
                    return true;
2259
                }
2260
            }
2261
2262
            self::$logger->debug('<<isTableOverloaded');
2263
            throw new BadTableNameException('The table name ['.$tablename.'] for the class ['.$classname.'] is invalid as it does not match a BO definition in the system!');
2264
        } else {
2265
            // check to see if there is already a "classname" column in the database for this BO
2266
2267
            $query = 'SHOW COLUMNS FROM '.$this->BO->getTableName();
2268
2269
            $result = self::getConnection()->query($query);
2270
2271
            if ($result) {
2272
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
2273
                    if ('classname' == $row['Field']) {
2274
                        self::$logger->debug('<<isTableOverloaded [true]');
2275
2276
                        return true;
2277
                    }
2278
                }
2279
            } else {
2280
                self::$logger->warn('Error during show columns ['.self::getConnection()->error.']');
2281
            }
2282
2283
            self::$logger->debug('<<isTableOverloaded [false]');
2284
2285
            return false;
2286
        }
2287
    }
2288
2289
    /**
2290
     * (non-PHPdoc).
2291
     *
2292
     * @see Alpha\Model\ActiveRecordProviderInterface::begin()
2293
     */
2294
    public static function begin()
2295
    {
2296
        if (self::$logger == null) {
2297
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2298
        }
2299
        self::$logger->debug('>>begin()');
2300
2301
        if (!self::getConnection()->autocommit(false)) {
2302
            throw new AlphaException('Error beginning a new transaction, error is ['.self::getConnection()->error.']');
2303
        }
2304
2305
        self::$logger->debug('<<begin');
2306
    }
2307
2308
    /**
2309
     * (non-PHPdoc).
2310
     *
2311
     * @see Alpha\Model\ActiveRecordProviderInterface::commit()
2312
     */
2313
    public static function commit()
2314
    {
2315
        if (self::$logger == null) {
2316
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2317
        }
2318
        self::$logger->debug('>>commit()');
2319
2320
        if (!self::getConnection()->commit()) {
2321
            throw new FailedSaveException('Error commiting a transaction, error is ['.self::getConnection()->error.']');
2322
        }
2323
2324
        self::$logger->debug('<<commit');
2325
    }
2326
2327
    /**
2328
     * (non-PHPdoc).
2329
     *
2330
     * @see Alpha\Model\ActiveRecordProviderInterface::rollback()
2331
     */
2332
    public static function rollback()
2333
    {
2334
        if (self::$logger == null) {
2335
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2336
        }
2337
        self::$logger->debug('>>rollback()');
2338
2339
        if (!self::getConnection()->rollback()) {
2340
            throw new AlphaException('Error rolling back a transaction, error is ['.self::getConnection()->error.']');
2341
        }
2342
2343
        self::$logger->debug('<<rollback');
2344
    }
2345
2346
    /**
2347
     * (non-PHPdoc).
2348
     *
2349
     * @see Alpha\Model\ActiveRecordProviderInterface::setBO()
2350
     */
2351
    public function setBO($BO)
2352
    {
2353
        $this->BO = $BO;
2354
    }
2355
2356
    /**
2357
     * Dynamically binds all of the attributes for the current BO to the supplied prepared statement
2358
     * parameters.  If arrays of attribute names and values are provided, only those will be bound to
2359
     * the supplied statement.
2360
     *
2361
     * @param mysqli_stmt $stmt The SQL statement to bind to.
2362
     * @param array Optional array of BO attributes.
2363
     * @param array Optional array of BO values.
2364
     *
2365
     * @return mysqli_stmt
2366
     *
2367
     * @since 1.1
2368
     */
2369
    private function bindParams($stmt, $attributes = array(), $values = array())
2370
    {
2371
        self::$logger->debug('>>bindParams(stmt=['.var_export($stmt, true).'])');
2372
2373
        $bindingsTypes = '';
2374
        $params = array();
2375
2376
        // here we are only binding the supplied attributes
2377
        if (count($attributes) > 0 && count($attributes) == count($values)) {
2378
            $count = count($values);
2379
2380
            for ($i = 0; $i < $count; ++$i) {
2381
                if (Validator::isInteger($values[$i])) {
2382
                    $bindingsTypes .= 'i';
2383
                } else {
2384
                    $bindingsTypes .= 's';
2385
                }
2386
                array_push($params, $values[$i]);
2387
            }
2388
2389
            if ($this->BO->isTableOverloaded()) {
2390
                $bindingsTypes .= 's';
2391
                array_push($params, get_class($this->BO));
2392
            }
2393
        } else { // bind all attributes on the business object
2394
2395
            // get the class attributes
2396
            $reflection = new ReflectionClass(get_class($this->BO));
2397
            $properties = $reflection->getProperties();
2398
2399
            foreach ($properties as $propObj) {
2400
                $propName = $propObj->name;
2401
                if (!in_array($propName, $this->BO->getTransientAttributes())) {
2402
                    // Skip the OID, database auto number takes care of this.
2403
                    if ($propName != 'OID' && $propName != 'version_num') {
2404
                        if ($this->BO->getPropObject($propName) instanceof Integer) {
2405
                            $bindingsTypes .= 'i';
2406
                        } else {
2407
                            $bindingsTypes .= 's';
2408
                        }
2409
                        array_push($params, $this->BO->get($propName));
2410
                    }
2411
2412
                    if ($propName == 'version_num') {
2413
                        $temp = $this->BO->getVersionNumber()->getValue();
2414
                        $this->BO->set('version_num', $temp + 1);
2415
                        $bindingsTypes .= 'i';
2416
                        array_push($params, $this->BO->getVersionNumber()->getValue());
2417
                    }
2418
                }
2419
            }
2420
2421
            if ($this->BO->isTableOverloaded()) {
2422
                $bindingsTypes .= 's';
2423
                array_push($params, get_class($this->BO));
2424
            }
2425
2426
            // the OID may be on the WHERE clause for UPDATEs and DELETEs
2427
            if (!$this->BO->isTransient()) {
2428
                $bindingsTypes .= 'i';
2429
                array_push($params, $this->BO->getOID());
2430
            }
2431
        }
2432
2433
        self::$logger->debug('bindingsTypes=['.$bindingsTypes.'], count: ['.mb_strlen($bindingsTypes).']');
2434
        self::$logger->debug('params ['.var_export($params, true).']');
2435
2436
        if ($params != null) {
2437
            $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...
2438
2439
            $count = count($params);
2440
2441
            for ($i = 0; $i < $count; ++$i) {
2442
                $bind_name = 'bind'.$i;
2443
                $$bind_name = $params[$i];
2444
                $bind_names[] = &$$bind_name;
2445
            }
2446
2447
            call_user_func_array(array($stmt, 'bind_param'), $bind_names);
2448
        }
2449
2450
        self::$logger->debug('<<bindParams ['.var_export($stmt, true).']');
2451
2452
        return $stmt;
2453
    }
2454
2455
    /**
2456
     * Dynamically binds the result of the supplied prepared statement to a 2d array, where each element in the array is another array
2457
     * representing a database row.
2458
     *
2459
     * @param mysqli_stmt $stmt
2460
     *
2461
     * @return array A 2D array containing the query result.
2462
     *
2463
     * @since 1.1
2464
     */
2465
    private function bindResult($stmt)
2466
    {
2467
        $result = array();
2468
2469
        $metadata = $stmt->result_metadata();
2470
        $fields = $metadata->fetch_fields();
2471
2472
        while (true) {
2473
            $pointers = array();
2474
            $row = array();
2475
2476
            $pointers[] = $stmt;
2477
            foreach ($fields as $field) {
2478
                $fieldname = $field->name;
2479
                $pointers[] = &$row[$fieldname];
2480
            }
2481
2482
            call_user_func_array('mysqli_stmt_bind_result', $pointers);
2483
2484
            if (!$stmt->fetch()) {
2485
                break;
2486
            }
2487
2488
            $result[] = $row;
2489
        }
2490
2491
        $metadata->free();
2492
2493
        return $result;
2494
    }
2495
2496
    /**
2497
     * Parses a MySQL error for the value that violated a unique constraint.
2498
     *
2499
     * @param string $error The MySQL error string.
2500
     *
2501
     * @since 1.1
2502
     */
2503
    private function findOffendingValue($error)
2504
    {
2505
        self::$logger->debug('>>findOffendingValue(error=['.$error.'])');
2506
2507
        $singleQuote1 = mb_strpos($error, "'");
2508
        $singleQuote2 = mb_strrpos($error, "'");
2509
2510
        $value = mb_substr($error, $singleQuote1, ($singleQuote2 - $singleQuote1) + 1);
2511
        self::$logger->debug('<<findOffendingValue ['.$value.'])');
2512
2513
        return $value;
2514
    }
2515
2516
    /**
2517
     * (non-PHPdoc).
2518
     *
2519
     * @see Alpha\Model\ActiveRecordProviderInterface::checkDatabaseExists()
2520
     */
2521
    public static function checkDatabaseExists()
2522
    {
2523
        $config = ConfigProvider::getInstance();
2524
2525
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2526
2527
        $result = $connection->query('SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \''.$config->get('db.name').'\'');
2528
2529
        if (count($result) > 0) {
2530
            return true;
2531
        } else {
2532
            return false;
2533
        }
2534
    }
2535
2536
    /**
2537
     * (non-PHPdoc).
2538
     *
2539
     * @see Alpha\Model\ActiveRecordProviderInterface::createDatabase()
2540
     */
2541
    public static function createDatabase()
2542
    {
2543
        $config = ConfigProvider::getInstance();
2544
2545
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2546
2547
        $connection->query('CREATE DATABASE '.$config->get('db.name'));
2548
    }
2549
2550
    /**
2551
     * (non-PHPdoc).
2552
     *
2553
     * @see Alpha\Model\ActiveRecordProviderInterface::dropDatabase()
2554
     */
2555
    public static function dropDatabase()
2556
    {
2557
        $config = ConfigProvider::getInstance();
2558
2559
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2560
2561
        $connection->query('DROP DATABASE '.$config->get('db.name'));
2562
    }
2563
}
2564