ActiveRecordProviderMySQL::loadAllByAttributes()   F
last analyzed

Complexity

Conditions 18
Paths 664

Size

Total Lines 109

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 109
rs 0.9333
cc 18
nc 664
nop 8

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Integer;
6
use Alpha\Model\Type\Timestamp;
7
use Alpha\Model\Type\DEnum;
8
use Alpha\Model\Type\Relation;
9
use Alpha\Model\Type\RelationLookup;
10
use Alpha\Model\Type\Double;
11
use Alpha\Model\Type\Text;
12
use Alpha\Model\Type\LargeText;
13
use Alpha\Model\Type\HugeText;
14
use Alpha\Model\Type\SmallText;
15
use Alpha\Model\Type\Date;
16
use Alpha\Model\Type\Enum;
17
use Alpha\Model\Type\Boolean;
18
use Alpha\Util\Config\ConfigProvider;
19
use Alpha\Util\Logging\Logger;
20
use Alpha\Util\Helper\Validator;
21
use Alpha\Util\Service\ServiceFactory;
22
use Alpha\Exception\AlphaException;
23
use Alpha\Exception\FailedSaveException;
24
use Alpha\Exception\FailedDeleteException;
25
use Alpha\Exception\FailedIndexCreateException;
26
use Alpha\Exception\LockingException;
27
use Alpha\Exception\ValidationException;
28
use Alpha\Exception\CustomQueryException;
29
use Alpha\Exception\RecordNotFoundException;
30
use Alpha\Exception\BadTableNameException;
31
use Alpha\Exception\ResourceNotAllowedException;
32
use Alpha\Exception\IllegalArguementException;
33
use Alpha\Exception\PHPException;
34
use Exception;
35
use ReflectionClass;
36
use Mysqli;
37
38
/**
39
 * MySQL active record provider (uses the MySQLi native API in PHP).
40
 *
41
 * @since 1.1
42
 *
43
 * @author John Collins <[email protected]>
44
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
45
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
46
 * All rights reserved.
47
 *
48
 * <pre>
49
 * Redistribution and use in source and binary forms, with or
50
 * without modification, are permitted provided that the
51
 * following conditions are met:
52
 *
53
 * * Redistributions of source code must retain the above
54
 *   copyright notice, this list of conditions and the
55
 *   following disclaimer.
56
 * * Redistributions in binary form must reproduce the above
57
 *   copyright notice, this list of conditions and the
58
 *   following disclaimer in the documentation and/or other
59
 *   materials provided with the distribution.
60
 * * Neither the name of the Alpha Framework nor the names
61
 *   of its contributors may be used to endorse or promote
62
 *   products derived from this software without specific
63
 *   prior written permission.
64
 *
65
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
66
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
67
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
68
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
69
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
70
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
71
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
72
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
73
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
74
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
75
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
76
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
77
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
78
 * </pre>
79
 */
80
class ActiveRecordProviderMySQL implements ActiveRecordProviderInterface
81
{
82
    /**
83
     * Trace logger.
84
     *
85
     * @var \Alpha\Util\Logging\Logger
86
     *
87
     * @since 1.1
88
     */
89
    private static $logger = null;
90
91
    /**
92
     * Datebase connection.
93
     *
94
     * @var Mysqli
95
     *
96
     * @since 1.1
97
     */
98
    private static $connection;
99
100
    /**
101
     * The business object that we are mapping back to.
102
     *
103
     * @var \Alpha\Model\ActiveRecord
104
     *
105
     * @since 1.1
106
     */
107
    private $record;
108
109
    /**
110
     * The constructor.
111
     *
112
     * @since 1.1
113
     */
114
    public function __construct()
115
    {
116
        self::$logger = new Logger('ActiveRecordProviderMySQL');
117
        self::$logger->debug('>>__construct()');
118
119
        self::$logger->debug('<<__construct');
120
    }
121
122
    /**
123
     * (non-PHPdoc).
124
     *
125
     * @see Alpha\Model\ActiveRecordProviderInterface::getConnection()
126
     */
127
    public static function getConnection()
128
    {
129
        $config = ConfigProvider::getInstance();
130
131
        if (!isset(self::$connection)) {
132
            try {
133
                self::$connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'), $config->get('db.name'));
134
            } catch (\Exception $e) {
135
                // if we failed to connect because the database does not exist, create it and try again
136
                if (strpos($e->getMessage(), 'HY000/1049') !== false) {
137
                    self::createDatabase();
138
                    self::$connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'), $config->get('db.name'));
139
                }
140
            }
141
142
            self::$connection->set_charset('utf8');
143
144
            if (mysqli_connect_error()) {
145
                self::$logger->fatal('Could not connect to database: ['.mysqli_connect_errno().'] '.mysqli_connect_error());
146
            }
147
        }
148
149
        return self::$connection;
150
    }
151
152
    /**
153
     * (non-PHPdoc).
154
     *
155
     * @see Alpha\Model\ActiveRecordProviderInterface::disconnect()
156
     */
157
    public static function disconnect()
158
    {
159
        if (isset(self::$connection)) {
160
            self::$connection->close();
161
            self::$connection = null;
162
        }
163
    }
164
165
    /**
166
     * (non-PHPdoc).
167
     *
168
     * @see Alpha\Model\ActiveRecordProviderInterface::getLastDatabaseError()
169
     */
170
    public static function getLastDatabaseError()
171
    {
172
        return self::getConnection()->error;
173
    }
174
175
    /**
176
     * (non-PHPdoc).
177
     *
178
     * @see Alpha\Model\ActiveRecordProviderInterface::query()
179
     */
180
    public function query($sqlQuery)
181
    {
182
        $this->record->setLastQuery($sqlQuery);
183
184
        $resultArray = array();
185
186
        if (!$result = self::getConnection()->query($sqlQuery)) {
187
            throw new CustomQueryException('Failed to run the custom query, MySql error is ['.self::getConnection()->error.'], query ['.$sqlQuery.']');
188
        } else {
189
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
190
                array_push($resultArray, $row);
191
            }
192
193
            return $resultArray;
194
        }
195
    }
196
197
    /**
198
     * (non-PHPdoc).
199
     *
200
     * @see Alpha\Model\ActiveRecordProviderInterface::load()
201
     */
202
    public function load($ID, $version = 0)
203
    {
204
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
205
206
        $attributes = $this->record->getPersistentAttributes();
207
        $fields = '';
208
        foreach ($attributes as $att) {
209
            $fields .= $att.',';
210
        }
211
        $fields = mb_substr($fields, 0, -1);
212
213
        if ($version > 0) {
214
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().'_history WHERE ID = ? AND version_num = ? LIMIT 1;';
215
        } else {
216
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE ID = ? LIMIT 1;';
217
        }
218
        $this->record->setLastQuery($sqlQuery);
219
        $stmt = self::getConnection()->stmt_init();
220
221
        $row = array();
222
223
        if ($stmt->prepare($sqlQuery)) {
224
            if ($version > 0) {
225
                $stmt->bind_param('ii', $ID, $version);
226
            } else {
227
                $stmt->bind_param('i', $ID);
228
            }
229
230
            $stmt->execute();
231
232
            $result = $this->bindResult($stmt);
233
            if (isset($result[0])) {
234
                $row = $result[0];
235
            }
236
237
            $stmt->close();
238
        } else {
239
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.'], ID is ['.print_r($ID, true).'], MySql error is ['.self::getConnection()->error.']');
240
            if (!$this->record->checkTableExists()) {
241
                $this->record->makeTable();
242
243
                throw new RecordNotFoundException('Failed to load object of ID ['.$ID.'], table ['.$this->record->getTableName().'] did not exist so had to create!');
244
            }
245
246
            return;
247
        }
248
249
        if (!isset($row['ID']) || $row['ID'] < 1) {
250
            self::$logger->debug('<<load');
251
            throw new RecordNotFoundException('Failed to load object of ID ['.$ID.'] not found in database.');
252
        }
253
254
        // get the class attributes
255
        $reflection = new ReflectionClass(get_class($this->record));
256
        $properties = $reflection->getProperties();
257
258
        try {
259
            foreach ($properties as $propObj) {
260
                $propName = $propObj->name;
261
262
                // filter transient attributes
263
                if (!in_array($propName, $this->record->getTransientAttributes())) {
264
                    $this->record->set($propName, $row[$propName]);
265
                } elseif (!$propObj->isPrivate() && $this->record->getPropObject($propName) instanceof Relation) {
266
                    $prop = $this->record->getPropObject($propName);
267
268
                    // handle the setting of ONE-TO-MANY relation values
269
                    if ($prop->getRelationType() == 'ONE-TO-MANY') {
270
                        $this->record->set($propObj->name, $this->record->getID());
271
                    }
272
273
                    // handle the setting of MANY-TO-ONE relation values
274
                    if ($prop->getRelationType() == 'MANY-TO-ONE' && isset($row[$propName])) {
275
                        $this->record->set($propObj->name, $row[$propName]);
276
                    }
277
                }
278
            }
279
        } catch (IllegalArguementException $e) {
280
            self::$logger->warn('Bad data stored in the table ['.$this->record->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
281
        } catch (PHPException $e) {
282
            // it is possible that the load failed due to the table not being up-to-date
283
            if ($this->record->checkTableNeedsUpdate()) {
284
                $missingFields = $this->record->findMissingFields();
285
286
                $count = count($missingFields);
287
288
                for ($i = 0; $i < $count; ++$i) {
289
                    $this->record->addProperty($missingFields[$i]);
290
                }
291
292
                self::$logger->warn('<<load');
293
                throw new RecordNotFoundException('Failed to load object of ID ['.$ID.'], table ['.$this->record->getTableName().'] was out of sync with the database so had to be updated!');
294
            }
295
        }
296
297
        self::$logger->debug('<<load ['.$ID.']');
298
    }
299
300
    /**
301
     * (non-PHPdoc).
302
     *
303
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllOldVersions()
304
     */
305
    public function loadAllOldVersions($ID)
306
    {
307
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
308
309
        if (!$this->record->getMaintainHistory()) {
310
            throw new RecordFoundException('loadAllOldVersions method called on an active record where no history is maintained!');
311
        }
312
313
        $sqlQuery = 'SELECT version_num FROM '.$this->record->getTableName().'_history WHERE ID = \''.$ID.'\' ORDER BY version_num;';
314
315
        $this->record->setLastQuery($sqlQuery);
316
317
        if (!$result = self::getConnection()->query($sqlQuery)) {
318
            self::$logger->debug('<<loadAllOldVersions [0]');
319
            throw new RecordNotFoundException('Failed to load object versions, MySQL error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
320
        }
321
322
        // now build an array of objects to be returned
323
        $objects = array();
324
        $count = 0;
325
        $RecordClass = get_class($this->record);
326
327
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
328
            try {
329
                $obj = new $RecordClass();
330
                $obj->load($ID, $row['version_num']);
331
                $objects[$count] = $obj;
332
                ++$count;
333
            } catch (ResourceNotAllowedException $e) {
334
                // the resource not allowed will be absent from the list
335
            }
336
        }
337
338
        self::$logger->debug('<<loadAllOldVersions ['.count($objects).']');
339
340
        return $objects;
341
    }
342
343
    /**
344
     * (non-PHPdoc).
345
     *
346
     * @see Alpha\Model\ActiveRecordProviderInterface::loadByAttribute()
347
     */
348
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
349
    {
350
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'],
351
			loadAttributes=['.var_export($loadAttributes, true).'])');
352
353
        if (count($loadAttributes) == 0) {
354
            $attributes = $this->record->getPersistentAttributes();
355
        } else {
356
            $attributes = $loadAttributes;
357
        }
358
359
        $fields = '';
360
        foreach ($attributes as $att) {
361
            $fields .= $att.',';
362
        }
363
        $fields = mb_substr($fields, 0, -1);
364
365
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
366
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE '.$attribute.' = ? AND classname = ? LIMIT 1;';
367
        } else {
368
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE '.$attribute.' = ? LIMIT 1;';
369
        }
370
371
        self::$logger->debug('Query=['.$sqlQuery.']');
372
373
        $this->record->setLastQuery($sqlQuery);
374
        $stmt = self::getConnection()->stmt_init();
375
376
        $row = array();
377
378
        if ($stmt->prepare($sqlQuery)) {
379
            if ($this->record->getPropObject($attribute) instanceof Integer) {
380
                if (!$ignoreClassType && $this->record->isTableOverloaded()) {
381
                    $classname = get_class($this->record);
382
                    $stmt->bind_param('is', $value, $classname);
383
                } else {
384
                    $stmt->bind_param('i', $value);
385
                }
386
            } else {
387
                if (!$ignoreClassType && $this->record->isTableOverloaded()) {
388
                    $classname = get_class($this->record);
389
                    $stmt->bind_param('ss', $value, $classname);
390
                } else {
391
                    $stmt->bind_param('s', $value);
392
                }
393
            }
394
395
            $stmt->execute();
396
397
            $result = $this->bindResult($stmt);
398
399
            if (isset($result[0])) {
400
                $row = $result[0];
401
            }
402
403
            $stmt->close();
404
        } else {
405
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
406
            if (!$this->record->checkTableExists()) {
407
                $this->record->makeTable();
408
409
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
410
            }
411
412
            return;
413
        }
414
415
        if (!isset($row['ID']) || $row['ID'] < 1) {
416
            self::$logger->debug('<<loadByAttribute');
417
            throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], not found in database.');
418
        }
419
420
        $this->record->setID($row['ID']);
421
422
        // get the class attributes
423
        $reflection = new ReflectionClass(get_class($this->record));
424
        $properties = $reflection->getProperties();
425
426
        try {
427
            foreach ($properties as $propObj) {
428
                $propName = $propObj->name;
429
430
                if (isset($row[$propName])) {
431
                    // filter transient attributes
432
                    if (!in_array($propName, $this->record->getTransientAttributes())) {
433
                        $this->record->set($propName, $row[$propName]);
434
                    } elseif (!$propObj->isPrivate() && $this->record->get($propName) != '' && $this->record->getPropObject($propName) instanceof Relation) {
435
                        $prop = $this->record->getPropObject($propName);
436
437
                        // handle the setting of ONE-TO-MANY relation values
438
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
439
                            $this->record->set($propObj->name, $this->record->getID());
440
                        }
441
                    }
442
                }
443
            }
444
        } catch (IllegalArguementException $e) {
445
            self::$logger->warn('Bad data stored in the table ['.$this->record->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
446
        } catch (PHPException $e) {
447
            // it is possible that the load failed due to the table not being up-to-date
448
            if ($this->record->checkTableNeedsUpdate()) {
449
                $missingFields = $this->record->findMissingFields();
450
451
                $count = count($missingFields);
452
453
                for ($i = 0; $i < $count; ++$i) {
454
                    $this->record->addProperty($missingFields[$i]);
455
                }
456
457
                self::$logger->debug('<<loadByAttribute');
458
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table ['.$this->record->getTableName().'] was out of sync with the database so had to be updated!');
459
            }
460
        }
461
462
        self::$logger->debug('<<loadByAttribute');
463
    }
464
465
    /**
466
     * (non-PHPdoc).
467
     *
468
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAll()
469
     */
470
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
471
    {
472
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
473
474
        // ensure that the field name provided in the orderBy param is legit
475
        try {
476
            $this->record->get($orderBy);
477
        } catch (AlphaException $e) {
478
            throw new AlphaException('The field name ['.$orderBy.'] provided in the param orderBy does not exist on the class ['.get_class($this->record).']');
479
        }
480
481
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
482
            if ($limit == 0) {
483
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE classname = \''.addslashes(get_class($this->record)).'\' ORDER BY '.$orderBy.' '.$order.';';
484
            } else {
485
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE classname = \''.addslashes(get_class($this->record)).'\' ORDER BY '.$orderBy.' '.$order.' LIMIT '.
486
                    $start.', '.$limit.';';
487
            }
488
        } else {
489
            if ($limit == 0) {
490
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' ORDER BY '.$orderBy.' '.$order.';';
491
            } else {
492
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' ORDER BY '.$orderBy.' '.$order.' LIMIT '.$start.', '.$limit.';';
493
            }
494
        }
495
496
        $this->record->setLastQuery($sqlQuery);
497
498
        if (!$result = self::getConnection()->query($sqlQuery)) {
499
            self::$logger->debug('<<loadAll [0]');
500
            throw new RecordNotFoundException('Failed to load object IDs, MySql error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
501
        }
502
503
        // now build an array of objects to be returned
504
        $objects = array();
505
        $count = 0;
506
        $RecordClass = get_class($this->record);
507
508
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
509
            try {
510
                $obj = new $RecordClass();
511
                $obj->load($row['ID']);
512
                $objects[$count] = $obj;
513
                ++$count;
514
            } catch (ResourceNotAllowedException $e) {
515
                // the resource not allowed will be absent from the list
516
            }
517
        }
518
519
        self::$logger->debug('<<loadAll ['.count($objects).']');
520
521
        return $objects;
522
    }
523
524
    /**
525
     * (non-PHPdoc).
526
     *
527
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttribute()
528
     */
529
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
530
    {
531
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
532
533
        if ($limit != 0) {
534
            $limit = ' LIMIT '.$start.', '.$limit.';';
535
        } else {
536
            $limit = ';';
537
        }
538
539
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
540
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE $attribute = ? AND classname = ? ORDER BY ".$orderBy.' '.$order.$limit;
541
        } else {
542
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE $attribute = ? ORDER BY ".$orderBy.' '.$order.$limit;
543
        }
544
545
        $this->record->setLastQuery($sqlQuery);
546
        self::$logger->debug($sqlQuery);
547
548
        $stmt = self::getConnection()->stmt_init();
549
550
        $row = array();
551
552
        if ($stmt->prepare($sqlQuery)) {
553
            if ($this->record->getPropObject($attribute) instanceof Integer) {
554
                if ($this->record->isTableOverloaded()) {
555
                    $classname = get_class($this->record);
556
                    $stmt->bind_param('is', $value, $classname);
557
                } else {
558
                    $stmt->bind_param('i', $value);
559
                }
560
            } else {
561
                if ($this->record->isTableOverloaded()) {
562
                    $classname = get_class($this->record);
563
                    $stmt->bind_param('ss', $value, $classname);
564
                } else {
565
                    $stmt->bind_param('s', $value);
566
                }
567
            }
568
569
            $stmt->execute();
570
571
            $result = $this->bindResult($stmt);
572
573
            $stmt->close();
574
        } else {
575
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
576
            if (!$this->record->checkTableExists()) {
577
                $this->record->makeTable();
578
579
                throw new RecordNotFoundException('Failed to load objects by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
580
            }
581
            self::$logger->debug('<<loadAllByAttribute []');
582
583
            return array();
584
        }
585
586
        // now build an array of objects to be returned
587
        $objects = array();
588
        $count = 0;
589
        $RecordClass = get_class($this->record);
590
591
        foreach ($result as $row) {
592
            try {
593
                $argsCount = count($constructorArgs);
594
595
                if ($argsCount < 1) {
596
                    $obj = new $RecordClass();
597
                } else {
598
                    switch ($argsCount) {
599
                        case 1:
600
                            $obj = new $RecordClass($constructorArgs[0]);
601
                        break;
602
                        case 2:
603
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
604
                        break;
605
                        case 3:
606
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
607
                        break;
608
                        case 4:
609
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
610
                        break;
611
                        case 5:
612
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
613
                        break;
614
                        default:
615
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
616
                    }
617
                }
618
619
                $obj->load($row['ID']);
620
                $objects[$count] = $obj;
621
                ++$count;
622
            } catch (ResourceNotAllowedException $e) {
623
                // the resource not allowed will be absent from the list
624
            }
625
        }
626
627
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
628
629
        return $objects;
630
    }
631
632
    /**
633
     * (non-PHPdoc).
634
     *
635
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttributes()
636
     */
637
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
638
    {
639
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
640
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
641
642
        $whereClause = ' WHERE';
643
644
        $count = count($attributes);
645
646
        for ($i = 0; $i < $count; ++$i) {
647
            $whereClause .= ' '.$attributes[$i].' = ? AND';
648
            self::$logger->debug($whereClause);
649
        }
650
651
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
652
            $whereClause .= ' classname = ? AND';
653
        }
654
655
        // remove the last " AND"
656
        $whereClause = mb_substr($whereClause, 0, -4);
657
658
        if ($limit != 0) {
659
            $limit = ' LIMIT '.$start.', '.$limit.';';
660
        } else {
661
            $limit = ';';
662
        }
663
664
        $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().$whereClause.' ORDER BY '.$orderBy.' '.$order.$limit;
665
666
        $this->record->setLastQuery($sqlQuery);
667
668
        $stmt = self::getConnection()->stmt_init();
669
670
        if ($stmt->prepare($sqlQuery)) {
671
            // bind params where required attributes are provided
672
            if (count($attributes) > 0 && count($attributes) == count($values)) {
673
                $stmt = $this->bindParams($stmt, $attributes, $values);
674
            } else {
675
                // we'll still need to bind the "classname" for overloaded records...
676
                if ($this->record->isTableOverloaded()) {
677
                    $classname = get_class($this->record);
678
                    $stmt->bind_param('s', $classname);
679
                }
680
            }
681
            $stmt->execute();
682
683
            $result = $this->bindResult($stmt);
684
685
            $stmt->close();
686
        } else {
687
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
688
689
            if (!$this->record->checkTableExists()) {
690
                $this->record->makeTable();
691
692
                throw new RecordNotFoundException('Failed to load objects by attributes ['.var_export($attributes, true).'] and values ['.
693
                    var_export($values, true).'], table did not exist so had to create!');
694
            }
695
696
            self::$logger->debug('<<loadAllByAttributes []');
697
698
            return array();
699
        }
700
701
        // now build an array of objects to be returned
702
        $objects = array();
703
        $count = 0;
704
        $RecordClass = get_class($this->record);
705
706
        foreach ($result as $row) {
707
            try {
708
                $argsCount = count($constructorArgs);
709
710
                if ($argsCount < 1) {
711
                    $obj = new $RecordClass();
712
                } else {
713
                    switch ($argsCount) {
714
                        case 1:
715
                            $obj = new $RecordClass($constructorArgs[0]);
716
                        break;
717
                        case 2:
718
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
719
                        break;
720
                        case 3:
721
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
722
                        break;
723
                        case 4:
724
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
725
                        break;
726
                        case 5:
727
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
728
                        break;
729
                        default:
730
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
731
                    }
732
                }
733
734
                $obj->load($row['ID']);
735
                $objects[$count] = $obj;
736
                ++$count;
737
            } catch (ResourceNotAllowedException $e) {
738
                // the resource not allowed will be absent from the list
739
            }
740
        }
741
742
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
743
744
        return $objects;
745
    }
746
747
    /**
748
     * (non-PHPdoc).
749
     *
750
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByDayUpdated()
751
     */
752
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
753
    {
754
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
755
756
        if ($start != 0 && $limit != 0) {
757
            $limit = ' LIMIT '.$start.', '.$limit.';';
758
        } else {
759
            $limit = ';';
760
        }
761
762
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
763
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' AND classname = '".addslashes(get_class($this->record))."' ORDER BY ".$orderBy.' '.$order.$limit;
764
        } else {
765
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' ORDER BY ".$orderBy.' '.$order.$limit;
766
        }
767
768
        $this->record->setLastQuery($sqlQuery);
769
770
        if (!$result = self::getConnection()->query($sqlQuery)) {
771
            self::$logger->debug('<<loadAllByDayUpdated []');
772
            throw new RecordNotFoundException('Failed to load object IDs, MySql error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
773
        }
774
775
        // now build an array of objects to be returned
776
        $objects = array();
777
        $count = 0;
778
        $RecordClass = get_class($this->record);
779
780
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
781
            $obj = new $RecordClass();
782
            $obj->load($row['ID']);
783
            $objects[$count] = $obj;
784
            ++$count;
785
        }
786
787
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
788
789
        return $objects;
790
    }
791
792
    /**
793
     * (non-PHPdoc).
794
     *
795
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllFieldValuesByAttribute()
796
     */
797
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
798
    {
799
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
800
801
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
802
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->record->getTableName()." WHERE $attribute = '$value' AND classname = '".addslashes(get_class($this->record))."' ORDER BY ID ".$order.';';
803
        } else {
804
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->record->getTableName()." WHERE $attribute = '$value' ORDER BY ID ".$order.';';
805
        }
806
807
        $this->record->setLastQuery($sqlQuery);
808
809
        self::$logger->debug('lastQuery ['.$sqlQuery.']');
810
811
        if (!$result = self::getConnection()->query($sqlQuery)) {
812
            self::$logger->debug('<<loadAllFieldValuesByAttribute []');
813
            throw new RecordNotFoundException('Failed to load field ['.$returnAttribute.'] values, MySql error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
814
        }
815
816
        // now build an array of attribute values to be returned
817
        $values = array();
818
        $count = 0;
819
820
        while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
821
            $values[$count] = $row[$returnAttribute];
822
            ++$count;
823
        }
824
825
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
826
827
        return $values;
828
    }
829
830
    /**
831
     * (non-PHPdoc).
832
     *
833
     * @see Alpha\Model\ActiveRecordProviderInterface::save()
834
     */
835
    public function save()
836
    {
837
        self::$logger->debug('>>save()');
838
839
        // get the class attributes
840
        $reflection = new ReflectionClass(get_class($this->record));
841
        $properties = $reflection->getProperties();
842
843
        // check to see if it is a transient object that needs to be inserted
844
        if ($this->record->isTransient()) {
845
            $savedFieldsCount = 0;
846
            $sqlQuery = 'INSERT INTO '.$this->record->getTableName().' (';
847
848
            foreach ($properties as $propObj) {
849
                $propName = $propObj->name;
850
                if (!in_array($propName, $this->record->getTransientAttributes())) {
851
                    // Skip the ID, database auto number takes care of this.
852
                    if ($propName != 'ID' && $propName != 'version_num') {
853
                        $sqlQuery .= "$propName,";
854
                        ++$savedFieldsCount;
855
                    }
856
857
                    if ($propName == 'version_num') {
858
                        $sqlQuery .= 'version_num,';
859
                        ++$savedFieldsCount;
860
                    }
861
                }
862
            }
863
864
            if ($this->record->isTableOverloaded()) {
865
                $sqlQuery .= 'classname,';
866
            }
867
868
            $sqlQuery = rtrim($sqlQuery, ',');
869
870
            $sqlQuery .= ') VALUES (';
871
872
            for ($i = 0; $i < $savedFieldsCount; ++$i) {
873
                $sqlQuery .= '?,';
874
            }
875
876
            if ($this->record->isTableOverloaded()) {
877
                $sqlQuery .= '?,';
878
            }
879
880
            $sqlQuery = rtrim($sqlQuery, ',').')';
881
882
            $this->record->setLastQuery($sqlQuery);
883
            self::$logger->debug('Query ['.$sqlQuery.']');
884
885
            $stmt = self::getConnection()->stmt_init();
886
887
            if ($stmt->prepare($sqlQuery)) {
888
                $stmt = $this->bindParams($stmt);
889
                $stmt->execute();
890
            } else {
891
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
892
            }
893
        } else {
894
            // assume that it is a persistent object that needs to be updated
895
            $savedFieldsCount = 0;
896
            $sqlQuery = 'UPDATE '.$this->record->getTableName().' SET ';
897
898
            foreach ($properties as $propObj) {
899
                $propName = $propObj->name;
900
                if (!in_array($propName, $this->record->getTransientAttributes())) {
901
                    // Skip the ID, database auto number takes care of this.
902
                    if ($propName != 'ID' && $propName != 'version_num') {
903
                        $sqlQuery .= "$propName = ?,";
904
                        ++$savedFieldsCount;
905
                    }
906
907
                    if ($propName == 'version_num') {
908
                        $sqlQuery .= 'version_num = ?,';
909
                        ++$savedFieldsCount;
910
                    }
911
                }
912
            }
913
914
            if ($this->record->isTableOverloaded()) {
915
                $sqlQuery .= 'classname = ?,';
916
            }
917
918
            $sqlQuery = rtrim($sqlQuery, ',');
919
920
            $sqlQuery .= ' WHERE ID = ?;';
921
922
            $this->record->setLastQuery($sqlQuery);
923
            $stmt = self::getConnection()->stmt_init();
924
925
            if ($stmt->prepare($sqlQuery)) {
926
                $this->bindParams($stmt);
927
                $stmt->execute();
928
            } else {
929
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
930
            }
931
        }
932
933
        if ($stmt != null && $stmt->error == '') {
934
            // populate the updated ID in case we just done an insert
935
            if ($this->record->isTransient()) {
936
                $this->record->setID(self::getConnection()->insert_id);
937
            }
938
939
            $this->record->saveRelations();
940
941
            $stmt->close();
942
        } else {
943
            // there has been an error, so decrement the version number back
944
            $temp = $this->record->getVersionNumber()->getValue();
945
            $this->record->set('version_num', $temp-1);
946
947
            // check for unique violations
948
            if (self::getConnection()->errno == '1062') {
949
                throw new ValidationException('Failed to save, the value '.$this->findOffendingValue(self::getConnection()->error).' is already in use!');
950
            } else {
951
                throw new FailedSaveException('Failed to save object, MySql error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
952
            }
953
        }
954
955
        if ($this->record->getMaintainHistory()) {
956
            $this->record->saveHistory();
957
        }
958
    }
959
960
    /**
961
     * (non-PHPdoc).
962
     *
963
     * @see Alpha\Model\ActiveRecordProviderInterface::saveAttribute()
964
     */
965
    public function saveAttribute($attribute, $value)
966
    {
967
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
968
969
        $config = ConfigProvider::getInstance();
970
        $sessionProvider = $config->get('session.provider.name');
971
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
972
973
        if ($this->record->getVersion() != $this->record->getVersionNumber()->getValue()) {
974
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
975
        }
976
977
        // set the "updated by" fields, we can only set the user id if someone is logged in
978
        if ($session->get('currentUser') != null) {
979
            $this->record->set('updated_by', $session->get('currentUser')->getID());
980
        }
981
982
        $this->record->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
983
984
        // assume that it is a persistent object that needs to be updated
985
        $sqlQuery = 'UPDATE '.$this->record->getTableName().' SET '.$attribute.' = ?, version_num = ? , updated_by = ?, updated_ts = ? WHERE ID = ?;';
986
987
        $this->record->setLastQuery($sqlQuery);
988
        $stmt = self::getConnection()->stmt_init();
989
990
        $newVersionNumber = $this->record->getVersionNumber()->getValue()+1;
991
992
        if ($stmt->prepare($sqlQuery)) {
993
            if ($this->record->getPropObject($attribute) instanceof Integer) {
994
                $bindingsType = 'i';
995
            } else {
996
                $bindingsType = 's';
997
            }
998
            $ID = $this->record->getID();
999
            $updatedBy = $this->record->get('updated_by');
1000
            $updatedTS = $this->record->get('updated_ts');
1001
            $stmt->bind_param($bindingsType.'iisi', $value, $newVersionNumber, $updatedBy, $updatedTS, $ID);
1002
            self::$logger->debug('Binding params ['.$bindingsType.'iisi, '.$value.', '.$newVersionNumber.', '.$updatedBy.', '.$updatedTS.', '.$ID.']');
1003
            $stmt->execute();
1004
        } else {
1005
            throw new FailedSaveException('Failed to save attribute, error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
1006
        }
1007
1008
        $stmt->close();
1009
1010
        $this->record->set($attribute, $value);
1011
        $this->record->set('version_num', $newVersionNumber);
1012
1013
        if ($this->record->getMaintainHistory()) {
1014
            $this->record->saveHistory();
1015
        }
1016
1017
        self::$logger->debug('<<saveAttribute');
1018
    }
1019
1020
    /**
1021
     * (non-PHPdoc).
1022
     *
1023
     * @see Alpha\Model\ActiveRecordProviderInterface::saveHistory()
1024
     */
1025
    public function saveHistory()
1026
    {
1027
        self::$logger->debug('>>saveHistory()');
1028
1029
        // get the class attributes
1030
        $reflection = new ReflectionClass(get_class($this->record));
1031
        $properties = $reflection->getProperties();
1032
1033
        $savedFieldsCount = 0;
1034
        $attributeNames = array();
1035
        $attributeValues = array();
1036
1037
        $sqlQuery = 'INSERT INTO '.$this->record->getTableName().'_history (';
1038
1039
        foreach ($properties as $propObj) {
1040
            $propName = $propObj->name;
1041
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1042
                $sqlQuery .= "$propName,";
1043
                $attributeNames[] = $propName;
1044
                $attributeValues[] = $this->record->get($propName);
1045
                ++$savedFieldsCount;
1046
            }
1047
        }
1048
1049
        if ($this->record->isTableOverloaded()) {
1050
            $sqlQuery .= 'classname,';
1051
        }
1052
1053
        $sqlQuery = rtrim($sqlQuery, ',');
1054
1055
        $sqlQuery .= ') VALUES (';
1056
1057
        for ($i = 0; $i < $savedFieldsCount; ++$i) {
1058
            $sqlQuery .= '?,';
1059
        }
1060
1061
        if ($this->record->isTableOverloaded()) {
1062
            $sqlQuery .= '?,';
1063
        }
1064
1065
        $sqlQuery = rtrim($sqlQuery, ',').')';
1066
1067
        $this->record->setLastQuery($sqlQuery);
1068
        self::$logger->debug('Query ['.$sqlQuery.']');
1069
1070
        $stmt = self::getConnection()->stmt_init();
1071
1072
        if ($stmt->prepare($sqlQuery)) {
1073
            $stmt = $this->bindParams($stmt, $attributeNames, $attributeValues);
1074
            $stmt->execute();
1075
        } else {
1076
            throw new FailedSaveException('Failed to save object history, error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
1077
        }
1078
    }
1079
1080
    /**
1081
     * (non-PHPdoc).
1082
     *
1083
     * @see Alpha\Model\ActiveRecordProviderInterface::delete()
1084
     */
1085
    public function delete()
1086
    {
1087
        self::$logger->debug('>>delete()');
1088
1089
        $sqlQuery = 'DELETE FROM '.$this->record->getTableName().' WHERE ID = ?;';
1090
1091
        $this->record->setLastQuery($sqlQuery);
1092
1093
        $stmt = self::getConnection()->stmt_init();
1094
1095
        if ($stmt->prepare($sqlQuery)) {
1096
            $ID = $this->record->getID();
1097
            $stmt->bind_param('i', $ID);
1098
            $stmt->execute();
1099
            self::$logger->debug('Deleted the object ['.$this->record->getID().'] of class ['.get_class($this->record).']');
1100
        } else {
1101
            throw new FailedDeleteException('Failed to delete object ['.$this->record->getID().'], error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
1102
        }
1103
1104
        $stmt->close();
1105
1106
        self::$logger->debug('<<delete');
1107
    }
1108
1109
    /**
1110
     * (non-PHPdoc).
1111
     *
1112
     * @see Alpha\Model\ActiveRecordProviderInterface::getVersion()
1113
     */
1114
    public function getVersion()
1115
    {
1116
        self::$logger->debug('>>getVersion()');
1117
1118
        $sqlQuery = 'SELECT version_num FROM '.$this->record->getTableName().' WHERE ID = ?;';
1119
        $this->record->setLastQuery($sqlQuery);
1120
1121
        $stmt = self::getConnection()->stmt_init();
1122
1123
        if ($stmt->prepare($sqlQuery)) {
1124
            $ID = $this->record->getID();
1125
            $stmt->bind_param('i', $ID);
1126
1127
            $stmt->execute();
1128
1129
            $result = $this->bindResult($stmt);
1130
            if (isset($result[0])) {
1131
                $row = $result[0];
1132
            }
1133
1134
            $stmt->close();
1135
        } else {
1136
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
1137
            if (!$this->record->checkTableExists()) {
1138
                $this->record->makeTable();
1139
1140
                throw new RecordNotFoundException('Failed to get the version number, table did not exist so had to create!');
1141
            }
1142
1143
            return;
1144
        }
1145
1146
        if (!isset($row['version_num']) || $row['version_num'] < 1) {
1147
            self::$logger->debug('<<getVersion [0]');
1148
1149
            return 0;
1150
        } else {
1151
            $version_num = $row['version_num'];
1152
1153
            self::$logger->debug('<<getVersion ['.$version_num.']');
1154
1155
            return $version_num;
1156
        }
1157
    }
1158
1159
    /**
1160
     * (non-PHPdoc).
1161
     *
1162
     * @see Alpha\Model\ActiveRecordProviderInterface::makeTable()
1163
     */
1164
    public function makeTable($checkIndexes = true)
1165
    {
1166
        self::$logger->debug('>>makeTable()');
1167
1168
        $sqlQuery = 'CREATE TABLE '.$this->record->getTableName().' (ID INT(11) ZEROFILL NOT NULL AUTO_INCREMENT,';
1169
1170
        // get the class attributes
1171
        $reflection = new ReflectionClass(get_class($this->record));
1172
        $properties = $reflection->getProperties();
1173
1174
        foreach ($properties as $propObj) {
1175
            $propName = $propObj->name;
1176
1177
            if (!in_array($propName, $this->record->getTransientAttributes()) && $propName != 'ID') {
1178
                $prop = $this->record->getPropObject($propName);
1179
1180
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1181
                    $sqlQuery .= "$propName INT(".$prop->getSize().') ZEROFILL NOT NULL,';
1182
                } elseif ($prop instanceof Integer) {
1183
                    $sqlQuery .= "$propName INT(".$prop->getSize().'),';
1184
                } elseif ($prop instanceof Double) {
1185
                    $sqlQuery .= "$propName DOUBLE(".$prop->getSize(true).'),';
1186
                } elseif ($prop instanceof SmallText) {
1187
                    $sqlQuery .= "$propName VARCHAR(".$prop->getSize().') CHARACTER SET utf8,';
1188
                } elseif ($prop instanceof Text) {
1189
                    $sqlQuery .= "$propName TEXT CHARACTER SET utf8,";
1190
                } elseif ($prop instanceof LargeText) {
1191
                    $sqlQuery .= "$propName MEDIUMTEXT CHARACTER SET utf8,";
1192
                } elseif ($prop instanceof HugeText) {
1193
                    $sqlQuery .= "$propName LONGTEXT CHARACTER SET utf8,";
1194
                } elseif ($prop instanceof Boolean) {
1195
                    $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1196
                } elseif ($prop instanceof Date) {
1197
                    $sqlQuery .= "$propName DATE,";
1198
                } elseif ($prop instanceof Timestamp) {
1199
                    $sqlQuery .= "$propName DATETIME,";
1200
                } elseif ($prop instanceof Enum) {
1201
                    $sqlQuery .= "$propName ENUM(";
1202
                    $enumVals = $prop->getOptions();
1203
                    foreach ($enumVals as $val) {
1204
                        $sqlQuery .= "'".$val."',";
1205
                    }
1206
                    $sqlQuery = rtrim($sqlQuery, ',');
1207
                    $sqlQuery .= ') CHARACTER SET utf8,';
1208
                } elseif ($prop instanceof DEnum) {
1209
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1210
                    $denum->saveIfNew();
1211
                    $sqlQuery .= "$propName INT(11) ZEROFILL,";
1212
                } elseif ($prop instanceof Relation) {
1213
                    $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1214
                } else {
1215
                    $sqlQuery .= '';
1216
                }
1217
            }
1218
        }
1219
        if ($this->record->isTableOverloaded()) {
1220
            $sqlQuery .= 'classname VARCHAR(100),';
1221
        }
1222
1223
        $sqlQuery .= 'PRIMARY KEY (ID)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1224
1225
        $this->record->setLastQuery($sqlQuery);
1226
1227
        if (!$result = self::getConnection()->query($sqlQuery)) {
1228
            self::$logger->debug('<<makeTable');
1229
            throw new AlphaException('Failed to create the table ['.$this->record->getTableName().'] for the class ['.get_class($this->record).'], database error is ['.self::getConnection()->error.']');
1230
        }
1231
1232
        // check the table indexes if any additional ones required
1233
        if ($checkIndexes) {
1234
            $this->checkIndexes();
1235
        }
1236
1237
        if ($this->record->getMaintainHistory()) {
1238
            $this->record->makeHistoryTable();
1239
        }
1240
1241
        self::$logger->debug('<<makeTable');
1242
    }
1243
1244
    /**
1245
     * (non-PHPdoc).
1246
     *
1247
     * @see Alpha\Model\ActiveRecordProviderInterface::makeHistoryTable()
1248
     */
1249
    public function makeHistoryTable()
1250
    {
1251
        self::$logger->debug('>>makeHistoryTable()');
1252
1253
        $sqlQuery = 'CREATE TABLE '.$this->record->getTableName().'_history (ID INT(11) ZEROFILL NOT NULL,';
1254
1255
        // get the class attributes
1256
        $reflection = new ReflectionClass(get_class($this->record));
1257
        $properties = $reflection->getProperties();
1258
1259
        foreach ($properties as $propObj) {
1260
            $propName = $propObj->name;
1261
1262
            if (!in_array($propName, $this->record->getTransientAttributes()) && $propName != 'ID') {
1263
                $prop = $this->record->getPropObject($propName);
1264
1265
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1266
                    $sqlQuery .= "$propName INT(".$prop->getSize().') ZEROFILL NOT NULL,';
1267
                } elseif ($prop instanceof Integer) {
1268
                    $sqlQuery .= "$propName INT(".$prop->getSize().'),';
1269
                } elseif ($prop instanceof Double) {
1270
                    $sqlQuery .= "$propName DOUBLE(".$prop->getSize(true).'),';
1271
                } elseif ($prop instanceof SmallText) {
1272
                    $sqlQuery .= "$propName VARCHAR(".$prop->getSize().') CHARACTER SET utf8,';
1273
                } elseif ($prop instanceof Text) {
1274
                    $sqlQuery .= "$propName TEXT CHARACTER SET utf8,";
1275
                } elseif ($prop instanceof LargeText) {
1276
                    $sqlQuery .= "$propName MEDIUMTEXT CHARACTER SET utf8,";
1277
                } elseif ($prop instanceof HugeText) {
1278
                    $sqlQuery .= "$propName LONGTEXT CHARACTER SET utf8,";
1279
                } elseif ($prop instanceof Boolean) {
1280
                    $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
1281
                } elseif ($prop instanceof Date) {
1282
                    $sqlQuery .= "$propName DATE,";
1283
                } elseif ($prop instanceof Timestamp) {
1284
                    $sqlQuery .= "$propName DATETIME,";
1285
                } elseif ($prop instanceof Enum) {
1286
                    $sqlQuery .= "$propName ENUM(";
1287
                    $enumVals = $prop->getOptions();
1288
                    foreach ($enumVals as $val) {
1289
                        $sqlQuery .= "'".$val."',";
1290
                    }
1291
                    $sqlQuery = rtrim($sqlQuery, ',');
1292
                    $sqlQuery .= ') CHARACTER SET utf8,';
1293
                } elseif ($prop instanceof DEnum) {
1294
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1295
                    $denum->saveIfNew();
1296
                    $sqlQuery .= "$propName INT(11) ZEROFILL,";
1297
                } elseif ($prop instanceof Relation) {
1298
                    $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
1299
                } else {
1300
                    $sqlQuery .= '';
1301
                }
1302
            }
1303
        }
1304
1305
        if ($this->record->isTableOverloaded()) {
1306
            $sqlQuery .= 'classname VARCHAR(100),';
1307
        }
1308
1309
        $sqlQuery .= 'PRIMARY KEY (ID, version_num)) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;';
1310
1311
        $this->record->setLastQuery($sqlQuery);
1312
1313
        if (!$result = self::getConnection()->query($sqlQuery)) {
1314
            self::$logger->debug('<<makeHistoryTable');
1315
            throw new AlphaException('Failed to create the table ['.$this->record->getTableName().'_history] for the class ['.get_class($this->record).'], database error is ['.self::getConnection()->error.']');
1316
        }
1317
1318
        self::$logger->debug('<<makeHistoryTable');
1319
    }
1320
1321
    /**
1322
     * (non-PHPdoc).
1323
     *
1324
     * @see Alpha\Model\ActiveRecordProviderInterface::rebuildTable()
1325
     */
1326
    public function rebuildTable()
1327
    {
1328
        self::$logger->debug('>>rebuildTable()');
1329
1330
        $sqlQuery = 'DROP TABLE IF EXISTS '.$this->record->getTableName().';';
1331
1332
        $this->record->setLastQuery($sqlQuery);
1333
1334
        if (!$result = self::getConnection()->query($sqlQuery)) {
1335
            self::$logger->debug('<<rebuildTable');
1336
            throw new AlphaException('Failed to drop the table ['.$this->record->getTableName().'] for the class ['.get_class($this->record).'], database error is ['.self::getConnection()->error.']');
1337
        }
1338
1339
        $this->record->makeTable();
1340
1341
        self::$logger->debug('<<rebuildTable');
1342
    }
1343
1344
    /**
1345
     * (non-PHPdoc).
1346
     *
1347
     * @see Alpha\Model\ActiveRecordProviderInterface::dropTable()
1348
     */
1349
    public function dropTable($tableName = null)
1350
    {
1351
        self::$logger->debug('>>dropTable()');
1352
1353
        if ($tableName === null) {
1354
            $tableName = $this->record->getTableName();
1355
        }
1356
1357
        $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.';';
1358
1359
        $this->record->setLastQuery($sqlQuery);
1360
1361
        if (!$result = self::getConnection()->query($sqlQuery)) {
1362
            self::$logger->debug('<<dropTable');
1363
            throw new AlphaException('Failed to drop the table ['.$tableName.'] for the class ['.get_class($this->record).'], query is ['.$this->record->getLastQuery().']');
1364
        }
1365
1366
        if ($this->record->getMaintainHistory()) {
1367
            $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.'_history;';
1368
1369
            $this->record->setLastQuery($sqlQuery);
1370
1371
            if (!$result = self::getConnection()->query($sqlQuery)) {
1372
                self::$logger->debug('<<dropTable');
1373
                throw new AlphaException('Failed to drop the table ['.$tableName.'_history] for the class ['.get_class($this->record).'], query is ['.$this->record->getLastQuery().']');
1374
            }
1375
        }
1376
1377
        self::$logger->debug('<<dropTable');
1378
    }
1379
1380
    /**
1381
     * (non-PHPdoc).
1382
     *
1383
     * @see Alpha\Model\ActiveRecordProviderInterface::addProperty()
1384
     */
1385
    public function addProperty($propName)
1386
    {
1387
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1388
1389
        $sqlQuery = 'ALTER TABLE '.$this->record->getTableName().' ADD ';
1390
1391
        if ($this->isTableOverloaded() && $propName == 'classname') {
1392
            $sqlQuery .= 'classname VARCHAR(100)';
1393
        } else {
1394
            if (!in_array($propName, $this->record->getDefaultAttributes()) && !in_array($propName, $this->record->getTransientAttributes())) {
1395
                $prop = $this->record->getPropObject($propName);
1396
1397
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1398
                    $sqlQuery .= "$propName INT(".$prop->getSize().') ZEROFILL NOT NULL';
1399
                } elseif ($prop instanceof Integer) {
1400
                    $sqlQuery .= "$propName INT(".$prop->getSize().')';
1401
                } elseif ($prop instanceof Double) {
1402
                    $sqlQuery .= "$propName DOUBLE(".$prop->getSize(true).')';
1403
                } elseif ($prop instanceof SmallText) {
1404
                    $sqlQuery .= "$propName VARCHAR(".$prop->getSize().') CHARACTER SET utf8';
1405
                } elseif ($prop instanceof Text) {
1406
                    $sqlQuery .= "$propName TEXT CHARACTER SET utf8";
1407
                } elseif ($prop instanceof Boolean) {
1408
                    $sqlQuery .= "$propName CHAR(1) DEFAULT '0'";
1409
                } elseif ($prop instanceof Date) {
1410
                    $sqlQuery .= "$propName DATE";
1411
                } elseif ($prop instanceof Timestamp) {
1412
                    $sqlQuery .= "$propName DATETIME";
1413
                } elseif ($prop instanceof Enum) {
1414
                    $sqlQuery .= "$propName ENUM(";
1415
                    $enumVals = $prop->getOptions();
1416
                    foreach ($enumVals as $val) {
1417
                        $sqlQuery .= "'".$val."',";
1418
                    }
1419
                    $sqlQuery = rtrim($sqlQuery, ',');
1420
                    $sqlQuery .= ') CHARACTER SET utf8';
1421
                } elseif ($prop instanceof DEnum) {
1422
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1423
                    $denum->saveIfNew();
1424
                    $sqlQuery .= "$propName INT(11) ZEROFILL";
1425
                } elseif ($prop instanceof Relation) {
1426
                    $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED";
1427
                } else {
1428
                    $sqlQuery .= '';
1429
                }
1430
            }
1431
        }
1432
1433
        $this->record->setLastQuery($sqlQuery);
1434
1435
        if (!$result = self::getConnection()->query($sqlQuery)) {
1436
            self::$logger->debug('<<addProperty');
1437
            throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1438
        } else {
1439
            self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->record->getTableName().'] table for the class ['.get_class($this->record).']');
1440
        }
1441
1442
        if ($this->record->getMaintainHistory()) {
1443
            $sqlQuery = str_replace($this->record->getTableName(), $this->record->getTableName().'_history', $sqlQuery);
1444
1445
            if (!$result = self::getConnection()->query($sqlQuery)) {
1446
                self::$logger->debug('<<addProperty');
1447
                throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->record->getTableName().'_history], query is ['.$this->record->getLastQuery().']');
1448
            } else {
1449
                self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->record->getTableName().'_history] table for the class ['.get_class($this->record).']');
1450
            }
1451
        }
1452
1453
        self::$logger->debug('<<addProperty');
1454
    }
1455
1456
    /**
1457
     * (non-PHPdoc).
1458
     *
1459
     * @see Alpha\Model\ActiveRecordProviderInterface::getMAX()
1460
     */
1461
    public function getMAX()
1462
    {
1463
        self::$logger->debug('>>getMAX()');
1464
1465
        $sqlQuery = 'SELECT MAX(ID) AS max_ID FROM '.$this->record->getTableName();
1466
1467
        $this->record->setLastQuery($sqlQuery);
1468
1469
        try {
1470
            $result = $this->record->query($sqlQuery);
1471
1472
            $row = $result[0];
1473
1474
            if (isset($row['max_ID'])) {
1475
                self::$logger->debug('<<getMAX ['.$row['max_ID'].']');
1476
1477
                return $row['max_ID'];
1478
            } else {
1479
                throw new AlphaException('Failed to get the MAX ID for the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1480
            }
1481
        } catch (\Exception $e) {
1482
            self::$logger->debug('<<getMAX');
1483
            throw new AlphaException($e->getMessage());
1484
        }
1485
    }
1486
1487
    /**
1488
     * (non-PHPdoc).
1489
     *
1490
     * @see Alpha\Model\ActiveRecordProviderInterface::getCount()
1491
     */
1492
    public function getCount($attributes = array(), $values = array())
1493
    {
1494
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1495
1496
        if ($this->record->isTableOverloaded()) {
1497
            $whereClause = ' WHERE classname = \''.addslashes(get_class($this->record)).'\' AND';
1498
        } else {
1499
            $whereClause = ' WHERE';
1500
        }
1501
1502
        $count = count($attributes);
1503
1504
        for ($i = 0; $i < $count; ++$i) {
1505
            $whereClause .= ' '.$attributes[$i].' = \''.$values[$i].'\' AND';
1506
            self::$logger->debug($whereClause);
1507
        }
1508
        // remove the last " AND"
1509
        $whereClause = mb_substr($whereClause, 0, -4);
1510
1511
        if ($whereClause != ' WHERE') {
1512
            $sqlQuery = 'SELECT COUNT(ID) AS class_count FROM '.$this->record->getTableName().$whereClause;
1513
        } else {
1514
            $sqlQuery = 'SELECT COUNT(ID) AS class_count FROM '.$this->record->getTableName();
1515
        }
1516
1517
        $this->record->setLastQuery($sqlQuery);
1518
1519
        $result = self::getConnection()->query($sqlQuery);
1520
1521
        if ($result) {
1522
            $row = $result->fetch_array(MYSQLI_ASSOC);
1523
1524
            self::$logger->debug('<<getCount ['.$row['class_count'].']');
1525
1526
            return $row['class_count'];
1527
        } else {
1528
            self::$logger->debug('<<getCount');
1529
            throw new AlphaException('Failed to get the count for the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1530
        }
1531
    }
1532
1533
    /**
1534
     * (non-PHPdoc).
1535
     *
1536
     * @see Alpha\Model\ActiveRecordProviderInterface::getHistoryCount()
1537
     */
1538
    public function getHistoryCount()
1539
    {
1540
        self::$logger->debug('>>getHistoryCount()');
1541
1542
        if (!$this->record->getMaintainHistory()) {
1543
            throw new AlphaException('getHistoryCount method called on a DAO where no history is maintained!');
1544
        }
1545
1546
        $sqlQuery = 'SELECT COUNT(ID) AS object_count FROM '.$this->record->getTableName().'_history WHERE ID='.$this->record->getID();
1547
1548
        $this->record->setLastQuery($sqlQuery);
1549
1550
        $result = self::getConnection()->query($sqlQuery);
1551
1552
        if ($result) {
1553
            $row = $result->fetch_array(MYSQLI_ASSOC);
1554
1555
            self::$logger->debug('<<getHistoryCount ['.$row['object_count'].']');
1556
1557
            return $row['object_count'];
1558
        } else {
1559
            self::$logger->debug('<<getHistoryCount');
1560
            throw new AlphaException('Failed to get the history count for the business object ['.$this->record->getID().'] from the table ['.$this->record->getTableName().'_history], query is ['.$this->record->getLastQuery().']');
1561
        }
1562
    }
1563
1564
    /**
1565
     * (non-PHPdoc).
1566
     *
1567
     * @see Alpha\Model\ActiveRecordProviderInterface::setEnumOptions()
1568
     * @since 1.1
1569
     */
1570
    public function setEnumOptions()
1571
    {
1572
        self::$logger->debug('>>setEnumOptions()');
1573
1574
        // get the class attributes
1575
        $reflection = new ReflectionClass(get_class($this->record));
1576
        $properties = $reflection->getProperties();
1577
1578
        // flag for any database errors
1579
        $dbError = false;
1580
1581
        foreach ($properties as $propObj) {
1582
            $propName = $propObj->name;
1583
            if (!in_array($propName, $this->record->getDefaultAttributes()) && !in_array($propName, $this->record->getTransientAttributes())) {
1584
                $propClass = get_class($this->record->getPropObject($propName));
1585
                if ($propClass == 'Alpha\Model\Type\Enum') {
1586
                    $sqlQuery = 'SHOW COLUMNS FROM '.$this->record->getTableName()." LIKE '$propName'";
1587
1588
                    $this->record->setLastQuery($sqlQuery);
1589
1590
                    $result = self::getConnection()->query($sqlQuery);
1591
1592
                    if ($result) {
1593
                        $row = $result->fetch_array(MYSQLI_NUM);
1594
                        $options = explode("','", preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $row[1]));
1595
1596
                        $this->record->getPropObject($propName)->setOptions($options);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method setOptions() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Enum. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1597
                    } else {
1598
                        $dbError = true;
1599
                        break;
1600
                    }
1601
                }
1602
            }
1603
        }
1604
1605
        if (!$dbError) {
1606
            if (method_exists($this, 'after_setEnumOptions_callback')) {
1607
                $this->{'after_setEnumOptions_callback'}();
1608
            }
1609
        } else {
1610
            throw new AlphaException('Failed to load enum options correctly for object instance of class ['.get_class($this).']');
1611
        }
1612
        self::$logger->debug('<<setEnumOptions');
1613
    }
1614
1615
    /**
1616
     * (non-PHPdoc).
1617
     *
1618
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableExists()
1619
     */
1620
    public function checkTableExists($checkHistoryTable = false)
1621
    {
1622
        self::$logger->debug('>>checkTableExists(checkHistoryTable=['.$checkHistoryTable.'])');
1623
1624
        $tableExists = false;
1625
1626
        $sqlQuery = 'SHOW TABLES;';
1627
        $this->record->setLastQuery($sqlQuery);
1628
1629
        $result = self::getConnection()->query($sqlQuery);
1630
1631
        if ($result) {
1632
            $tableName = ($checkHistoryTable ? $this->record->getTableName().'_history' : $this->record->getTableName());
1633
1634
            while ($row = $result->fetch_array(MYSQLI_NUM)) {
1635
                if (strtolower($row[0]) == mb_strtolower($tableName)) {
1636
                    $tableExists = true;
1637
                }
1638
            }
1639
1640
            self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1641
1642
            return $tableExists;
1643
        } else {
1644
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1645
        }
1646
    }
1647
1648
    /**
1649
     * (non-PHPdoc).
1650
     *
1651
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordTableExists()
1652
     */
1653
    public static function checkRecordTableExists($RecordClassName, $checkHistoryTable = false)
1654
    {
1655
        if (self::$logger == null) {
1656
            self::$logger = new Logger('ActiveRecordProviderMySQL');
1657
        }
1658
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$RecordClassName.'], checkHistoryTable=['.$checkHistoryTable.'])');
1659
1660
        if (!class_exists($RecordClassName)) {
1661
            throw new IllegalArguementException('The classname provided ['.$checkHistoryTable.'] is not defined!');
1662
        }
1663
1664
        $tableName = $RecordClassName::TABLE_NAME;
1665
1666
        if (empty($tableName)) {
1667
            $tableName = mb_substr($RecordClassName, 0, mb_strpos($RecordClassName, '_'));
1668
        }
1669
1670
        if ($checkHistoryTable) {
1671
            $tableName .= '_history';
1672
        }
1673
1674
        $tableExists = false;
1675
1676
        $sqlQuery = 'SHOW TABLES;';
1677
1678
        $result = self::getConnection()->query($sqlQuery);
1679
1680
        while ($row = $result->fetch_array(MYSQLI_NUM)) {
1681
            if ($row[0] == $tableName) {
1682
                $tableExists = true;
1683
            }
1684
        }
1685
1686
        if ($result) {
1687
            self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1688
1689
            return $tableExists;
1690
        } else {
1691
            self::$logger->debug('<<checkRecordTableExists');
1692
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1693
        }
1694
    }
1695
1696
    /**
1697
     * (non-PHPdoc).
1698
     *
1699
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableNeedsUpdate()
1700
     */
1701
    public function checkTableNeedsUpdate()
1702
    {
1703
        self::$logger->debug('>>checkTableNeedsUpdate()');
1704
1705
        $updateRequired = false;
1706
1707
        $matchCount = 0;
1708
1709
        $query = 'SHOW COLUMNS FROM '.$this->record->getTableName();
1710
        $result = self::getConnection()->query($query);
1711
        $this->record->setLastQuery($query);
1712
1713
        // get the class attributes
1714
        $reflection = new ReflectionClass(get_class($this->record));
1715
        $properties = $reflection->getProperties();
1716
1717
        foreach ($properties as $propObj) {
1718
            $propName = $propObj->name;
1719
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1720
                $foundMatch = false;
1721
1722
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1723
                    if ($propName == $row['Field']) {
1724
                        $foundMatch = true;
1725
                        break;
1726
                    }
1727
                }
1728
1729
                if (!$foundMatch) {
1730
                    --$matchCount;
1731
                }
1732
1733
                $result->data_seek(0);
1734
            }
1735
        }
1736
1737
        // check for the "classname" field in overloaded tables
1738
        if ($this->record->isTableOverloaded()) {
1739
            $foundMatch = false;
1740
1741
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1742
                if ('classname' == $row['Field']) {
1743
                    $foundMatch = true;
1744
                    break;
1745
                }
1746
            }
1747
            if (!$foundMatch) {
1748
                --$matchCount;
1749
            }
1750
        }
1751
1752
        if ($matchCount != 0) {
1753
            $updateRequired = true;
1754
        }
1755
1756
        if ($result) {
1757
            // check the table indexes
1758
            try {
1759
                $this->checkIndexes();
1760
            } catch (AlphaException $ae) {
1761
                self::$logger->warn("Error while checking database indexes:\n\n".$ae->getMessage());
1762
            }
1763
1764
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1765
1766
            return $updateRequired;
1767
        } else {
1768
            self::$logger->debug('<<checkTableNeedsUpdate');
1769
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1770
        }
1771
    }
1772
1773
    /**
1774
     * (non-PHPdoc).
1775
     *
1776
     * @see Alpha\Model\ActiveRecordProviderInterface::findMissingFields()
1777
     */
1778
    public function findMissingFields()
1779
    {
1780
        self::$logger->debug('>>findMissingFields()');
1781
1782
        $missingFields = array();
1783
        $matchCount = 0;
1784
1785
        $sqlQuery = 'SHOW COLUMNS FROM '.$this->record->getTableName();
1786
1787
        $result = self::getConnection()->query($sqlQuery);
1788
1789
        $this->record->setLastQuery($sqlQuery);
1790
1791
        // get the class attributes
1792
        $reflection = new ReflectionClass(get_class($this->record));
1793
        $properties = $reflection->getProperties();
1794
1795
        foreach ($properties as $propObj) {
1796
            $propName = $propObj->name;
1797
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1798
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1799
                    if ($propName == $row['Field']) {
1800
                        ++$matchCount;
1801
                        break;
1802
                    }
1803
                }
1804
                $result->data_seek(0);
1805
            } else {
1806
                ++$matchCount;
1807
            }
1808
1809
            if ($matchCount == 0) {
1810
                array_push($missingFields, $propName);
1811
            } else {
1812
                $matchCount = 0;
1813
            }
1814
        }
1815
1816
        // check for the "classname" field in overloaded tables
1817
        if ($this->record->isTableOverloaded()) {
1818
            $foundMatch = false;
1819
1820
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1821
                if ('classname' == $row['Field']) {
1822
                    $foundMatch = true;
1823
                    break;
1824
                }
1825
            }
1826
            if (!$foundMatch) {
1827
                array_push($missingFields, 'classname');
1828
            }
1829
        }
1830
1831
        if (!$result) {
1832
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1833
        }
1834
1835
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1836
1837
        return $missingFields;
1838
    }
1839
1840
    /**
1841
     * (non-PHPdoc).
1842
     *
1843
     * @see Alpha\Model\ActiveRecordProviderInterface::getIndexes()
1844
     */
1845
    public function getIndexes()
1846
    {
1847
        self::$logger->debug('>>getIndexes()');
1848
1849
        $query = 'SHOW INDEX FROM '.$this->record->getTableName();
1850
1851
        $result = self::getConnection()->query($query);
1852
1853
        $this->record->setLastQuery($query);
1854
1855
        $indexNames = array();
1856
1857
        if (!$result) {
1858
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getConnection()->error.']');
1859
        } else {
1860
            while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
1861
                array_push($indexNames, $row['Key_name']);
1862
            }
1863
        }
1864
1865
        self::$logger->debug('<<getIndexes');
1866
1867
        return $indexNames;
1868
    }
1869
1870
    /**
1871
     * Checks to see if all of the indexes are in place for the record's table, creates those that are missing.
1872
     *
1873
     * @since 1.1
1874
     */
1875
    private function checkIndexes()
1876
    {
1877
        self::$logger->debug('>>checkIndexes()');
1878
1879
        $indexNames = $this->getIndexes();
1880
1881
        // process unique keys
1882
        foreach ($this->record->getUniqueAttributes() as $prop) {
1883
            // check for composite indexes
1884
            if (mb_strpos($prop, '+')) {
1885
                $attributes = explode('+', $prop);
1886
1887
                $index_exists = false;
1888
                foreach ($indexNames as $index) {
1889
                    if ($attributes[0].'_'.$attributes[1].'_unq_idx' == $index) {
1890
                        $index_exists = true;
1891
                    }
1892
                    if (count($attributes) == 3) {
1893
                        if ($attributes[0].'_'.$attributes[1].'_'.$attributes[2].'_unq_idx' == $index) {
1894
                            $index_exists = true;
1895
                        }
1896
                    }
1897
                }
1898
1899
                if (!$index_exists) {
1900
                    if (count($attributes) == 3) {
1901
                        $this->record->createUniqueIndex($attributes[0], $attributes[1], $attributes[2]);
1902
                    } else {
1903
                        $this->record->createUniqueIndex($attributes[0], $attributes[1]);
1904
                    }
1905
                }
1906
            } else {
1907
                $index_exists = false;
1908
                foreach ($indexNames as $index) {
1909
                    if ($prop.'_unq_idx' == $index) {
1910
                        $index_exists = true;
1911
                    }
1912
                }
1913
1914
                if (!$index_exists) {
1915
                    $this->createUniqueIndex($prop);
1916
                }
1917
            }
1918
        }
1919
1920
        // process foreign-key indexes
1921
        // get the class attributes
1922
        $reflection = new ReflectionClass(get_class($this->record));
1923
        $properties = $reflection->getProperties();
1924
1925
        foreach ($properties as $propObj) {
1926
            $propName = $propObj->name;
1927
            $prop = $this->record->getPropObject($propName);
1928
            if ($prop instanceof Relation) {
1929
                if ($prop->getRelationType() == 'MANY-TO-ONE') {
1930
                    $indexExists = false;
1931
                    foreach ($indexNames as $index) {
1932
                        if ($this->record->getTableName().'_'.$propName.'_fk_idx' == $index) {
1933
                            $indexExists = true;
1934
                        }
1935
                    }
1936
1937
                    if (!$indexExists) {
1938
                        $this->createForeignIndex($propName, $prop->getRelatedClass(), $prop->getRelatedClassField());
1939
                    }
1940
                }
1941
1942
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
1943
                    $lookup = $prop->getLookup();
1944
1945
                    if ($lookup != null) {
1946
                        try {
1947
                            $lookupIndexNames = $lookup->getIndexes();
1948
1949
                            // handle index check/creation on left side of Relation
1950
                            $indexExists = false;
1951
                            foreach ($lookupIndexNames as $index) {
1952
                                if ($lookup->getTableName().'_leftID_fk_idx' == $index) {
1953
                                    $indexExists = true;
1954
                                }
1955
                            }
1956
1957
                            if (!$indexExists) {
1958
                                $lookup->createForeignIndex('leftID', $prop->getRelatedClass('left'), 'ID');
1959
                            }
1960
1961
                            // handle index check/creation on right side of Relation
1962
                            $indexExists = false;
1963
                            foreach ($lookupIndexNames as $index) {
1964
                                if ($lookup->getTableName().'_rightID_fk_idx' == $index) {
1965
                                    $indexExists = true;
1966
                                }
1967
                            }
1968
1969
                            if (!$indexExists) {
1970
                                $lookup->createForeignIndex('rightID', $prop->getRelatedClass('right'), 'ID');
1971
                            }
1972
                        } catch (AlphaException $e) {
1973
                            self::$logger->error($e->getMessage());
1974
                        }
1975
                    }
1976
                }
1977
            }
1978
        }
1979
1980
        self::$logger->debug('<<checkIndexes');
1981
    }
1982
1983
    /**
1984
     * (non-PHPdoc).
1985
     *
1986
     * @see Alpha\Model\ActiveRecordProviderInterface::createForeignIndex()
1987
     */
1988
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
1989
    {
1990
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
1991
1992
        $relatedRecord = new $relatedClass();
1993
        $tableName = $relatedRecord->getTableName();
1994
1995
        $result = false;
1996
1997
        if (self::checkRecordTableExists($relatedClass)) {
1998
            $sqlQuery = '';
1999
2000
            if ($attributeName == 'leftID') {
2001
                if ($indexName === null) {
2002
                    $indexName = $this->record->getTableName().'_leftID_fk_idx';
2003
                }
2004
                $sqlQuery = 'ALTER TABLE '.$this->record->getTableName().' ADD INDEX '.$indexName.' (leftID);';
2005
            }
2006
            if ($attributeName == 'rightID') {
2007
                if ($indexName === null) {
2008
                    $indexName = $this->record->getTableName().'_rightID_fk_idx';
2009
                }
2010
                $sqlQuery = 'ALTER TABLE '.$this->record->getTableName().' ADD INDEX '.$indexName.' (rightID);';
2011
            }
2012
2013
            if (!empty($sqlQuery)) {
2014
                $this->record->setLastQuery($sqlQuery);
2015
2016
                $result = self::getConnection()->query($sqlQuery);
2017
2018
                if (!$result) {
2019
                    throw new FailedIndexCreateException('Failed to create an index on ['.$this->record->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
2020
                }
2021
            }
2022
2023
            if ($indexName === null) {
2024
                $indexName = $this->record->getTableName().'_'.$attributeName.'_fk_idx';
2025
            }
2026
2027
            $sqlQuery = 'ALTER TABLE '.$this->record->getTableName().' ADD FOREIGN KEY '.$indexName.' ('.$attributeName.') REFERENCES '.$tableName.' ('.$relatedClassAttribute.') ON DELETE SET NULL;';
2028
2029
            $this->record->setLastQuery($sqlQuery);
2030
            $result = self::getConnection()->query($sqlQuery);
2031
        }
2032
2033
        if ($result) {
2034
            self::$logger->debug('Successfully created the foreign key index ['.$indexName.']');
2035
        } else {
2036
            throw new FailedIndexCreateException('Failed to create the index ['.$indexName.'] on ['.$this->record->getTableName().'], error is ['.self::getConnection()->error.'], query ['.$this->record->getLastQuery().']');
2037
        }
2038
2039
        self::$logger->debug('<<createForeignIndex');
2040
    }
2041
2042
    /**
2043
     * (non-PHPdoc).
2044
     *
2045
     * @see Alpha\Model\ActiveRecordProviderInterface::createUniqueIndex()
2046
     */
2047
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
2048
    {
2049
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
2050
2051
        $sqlQuery = '';
2052
2053
        if ($attribute2Name != '' && $attribute3Name != '') {
2054
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_'.$attribute3Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.','.$attribute2Name.','.$attribute3Name.');';
2055
        }
2056
2057
        if ($attribute2Name != '' && $attribute3Name == '') {
2058
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.','.$attribute2Name.');';
2059
        }
2060
2061
        if ($attribute2Name == '' && $attribute3Name == '') {
2062
            $sqlQuery = 'CREATE UNIQUE INDEX '.$attribute1Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.');';
2063
        }
2064
2065
        $this->record->setLastQuery($sqlQuery);
2066
2067
        $result = self::getConnection()->query($sqlQuery);
2068
2069
        if ($result) {
2070
            self::$logger->debug('Successfully created the unique index on ['.$this->record->getTableName().']');
2071
        } else {
2072
            throw new FailedIndexCreateException('Failed to create the unique index on ['.$this->record->getTableName().'], error is ['.self::getConnection()->error.']');
2073
        }
2074
2075
        self::$logger->debug('<<createUniqueIndex');
2076
    }
2077
2078
    /**
2079
     * (non-PHPdoc).
2080
     *
2081
     * @see Alpha\Model\ActiveRecordProviderInterface::reload()
2082
     */
2083
    public function reload()
2084
    {
2085
        self::$logger->debug('>>reload()');
2086
2087
        if (!$this->record->isTransient()) {
2088
            $this->record->load($this->record->getID());
2089
        } else {
2090
            throw new AlphaException('Cannot reload transient object from database!');
2091
        }
2092
        self::$logger->debug('<<reload');
2093
    }
2094
2095
    /**
2096
     * (non-PHPdoc).
2097
     *
2098
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordExists()
2099
     */
2100
    public function checkRecordExists($ID)
2101
    {
2102
        self::$logger->debug('>>checkRecordExists(ID=['.$ID.'])');
2103
2104
        $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE ID = ?;';
2105
2106
        $this->record->setLastQuery($sqlQuery);
2107
2108
        $stmt = self::getConnection()->stmt_init();
2109
2110
        if ($stmt->prepare($sqlQuery)) {
2111
            $stmt->bind_param('i', $ID);
2112
2113
            $stmt->execute();
2114
2115
            $result = $this->bindResult($stmt);
2116
2117
            $stmt->close();
2118
2119
            if (is_array($result)) {
2120
                if (count($result) > 0) {
2121
                    self::$logger->debug('<<checkRecordExists [true]');
2122
2123
                    return true;
2124
                } else {
2125
                    self::$logger->debug('<<checkRecordExists [false]');
2126
2127
                    return false;
2128
                }
2129
            } else {
2130
                self::$logger->debug('<<checkRecordExists');
2131
                throw new AlphaException('Failed to check for the record ['.$ID.'] on the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
2132
            }
2133
        } else {
2134
            self::$logger->debug('<<checkRecordExists');
2135
            throw new AlphaException('Failed to check for the record ['.$ID.'] on the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
2136
        }
2137
    }
2138
2139
    /**
2140
     * (non-PHPdoc).
2141
     *
2142
     * @see Alpha\Model\ActiveRecordProviderInterface::isTableOverloaded()
2143
     */
2144
    public function isTableOverloaded()
2145
    {
2146
        self::$logger->debug('>>isTableOverloaded()');
2147
2148
        $reflection = new ReflectionClass($this->record);
2149
        $classname = $reflection->getShortName();
2150
        $tablename = ucfirst($this->record->getTableName());
2151
2152
        // use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
2153
        $implementedInterfaces = $reflection->getInterfaces();
2154
2155
        foreach ($implementedInterfaces as $interface) {
2156
            if ($interface->name == 'Alpha\Model\Type\TypeInterface') {
2157
                self::$logger->debug('<<isTableOverloaded [false]');
2158
2159
                return false;
2160
            }
2161
        }
2162
2163
        if ($classname != $tablename) {
2164
            // loop over all records to see if there is one using the same table as this record
2165
2166
            $Recordclasses = ActiveRecord::getRecordClassNames();
2167
2168
            foreach ($Recordclasses as $RecordclassName) {
2169
                $reflection = new ReflectionClass($RecordclassName);
2170
                $classname = $reflection->getShortName();
2171
                if ($tablename == $classname) {
2172
                    self::$logger->debug('<<isTableOverloaded [true]');
2173
2174
                    return true;
2175
                }
2176
            }
2177
2178
            self::$logger->debug('<<isTableOverloaded');
2179
            throw new BadTableNameException('The table name ['.$tablename.'] for the class ['.$classname.'] is invalid as it does not match a Record definition in the system!');
2180
        } else {
2181
            // check to see if there is already a "classname" column in the database for this record
2182
2183
            $query = 'SHOW COLUMNS FROM '.$this->record->getTableName();
2184
2185
            $result = self::getConnection()->query($query);
2186
2187
            if ($result) {
2188
                while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
2189
                    if ('classname' == $row['Field']) {
2190
                        self::$logger->debug('<<isTableOverloaded [true]');
2191
2192
                        return true;
2193
                    }
2194
                }
2195
            } else {
2196
                self::$logger->warn('Error during show columns ['.self::getConnection()->error.']');
2197
            }
2198
2199
            self::$logger->debug('<<isTableOverloaded [false]');
2200
2201
            return false;
2202
        }
2203
    }
2204
2205
    /**
2206
     * (non-PHPdoc).
2207
     *
2208
     * @see Alpha\Model\ActiveRecordProviderInterface::begin()
2209
     */
2210
    public static function begin()
2211
    {
2212
        if (self::$logger == null) {
2213
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2214
        }
2215
        self::$logger->debug('>>begin()');
2216
2217
        if (!self::getConnection()->autocommit(false)) {
2218
            throw new AlphaException('Error beginning a new transaction, error is ['.self::getConnection()->error.']');
2219
        }
2220
2221
        self::$logger->debug('<<begin');
2222
    }
2223
2224
    /**
2225
     * (non-PHPdoc).
2226
     *
2227
     * @see Alpha\Model\ActiveRecordProviderInterface::commit()
2228
     */
2229
    public static function commit()
2230
    {
2231
        if (self::$logger == null) {
2232
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2233
        }
2234
        self::$logger->debug('>>commit()');
2235
2236
        if (!self::getConnection()->commit()) {
2237
            throw new FailedSaveException('Error commiting a transaction, error is ['.self::getConnection()->error.']');
2238
        }
2239
2240
        self::$logger->debug('<<commit');
2241
    }
2242
2243
    /**
2244
     * (non-PHPdoc).
2245
     *
2246
     * @see Alpha\Model\ActiveRecordProviderInterface::rollback()
2247
     */
2248
    public static function rollback()
2249
    {
2250
        if (self::$logger == null) {
2251
            self::$logger = new Logger('ActiveRecordProviderMySQL');
2252
        }
2253
        self::$logger->debug('>>rollback()');
2254
2255
        if (!self::getConnection()->rollback()) {
2256
            throw new AlphaException('Error rolling back a transaction, error is ['.self::getConnection()->error.']');
2257
        }
2258
2259
        self::$logger->debug('<<rollback');
2260
    }
2261
2262
    /**
2263
     * (non-PHPdoc).
2264
     *
2265
     * @see Alpha\Model\ActiveRecordProviderInterface::setRecord()
2266
     */
2267
    public function setRecord($Record)
2268
    {
2269
        $this->record = $Record;
2270
    }
2271
2272
    /**
2273
     * Dynamically binds all of the attributes for the current Record to the supplied prepared statement
2274
     * parameters.  If arrays of attribute names and values are provided, only those will be bound to
2275
     * the supplied statement.
2276
     *
2277
     * @param \mysqli_stmt $stmt The SQL statement to bind to.
2278
     * @param array Optional array of Record attributes.
2279
     * @param array Optional array of Record values.
2280
     *
2281
     * @return \mysqli_stmt
2282
     *
2283
     * @since 1.1
2284
     */
2285
    private function bindParams($stmt, $attributes = array(), $values = array())
2286
    {
2287
        self::$logger->debug('>>bindParams(stmt=['.var_export($stmt, true).'])');
2288
2289
        $bindingsTypes = '';
2290
        $params = array();
2291
2292
        // here we are only binding the supplied attributes
2293
        if (count($attributes) > 0 && count($attributes) == count($values)) {
2294
            $count = count($values);
2295
2296
            for ($i = 0; $i < $count; ++$i) {
2297
                if (Validator::isInteger($values[$i])) {
2298
                    $bindingsTypes .= 'i';
2299
                } else {
2300
                    $bindingsTypes .= 's';
2301
                }
2302
                array_push($params, $values[$i]);
2303
            }
2304
2305
            if ($this->record->isTableOverloaded()) {
2306
                $bindingsTypes .= 's';
2307
                array_push($params, get_class($this->record));
2308
            }
2309
        } else { // bind all attributes on the business object
2310
2311
            // get the class attributes
2312
            $reflection = new ReflectionClass(get_class($this->record));
2313
            $properties = $reflection->getProperties();
2314
2315
            foreach ($properties as $propObj) {
2316
                $propName = $propObj->name;
2317
                if (!in_array($propName, $this->record->getTransientAttributes())) {
2318
                    // Skip the ID, database auto number takes care of this.
2319
                    if ($propName != 'ID' && $propName != 'version_num') {
2320
                        if ($this->record->getPropObject($propName) instanceof Integer) {
2321
                            $bindingsTypes .= 'i';
2322
                        } else {
2323
                            $bindingsTypes .= 's';
2324
                        }
2325
                        array_push($params, $this->record->get($propName));
2326
                    }
2327
2328
                    if ($propName == 'version_num') {
2329
                        $temp = $this->record->getVersionNumber()->getValue();
2330
                        $this->record->set('version_num', $temp+1);
2331
                        $bindingsTypes .= 'i';
2332
                        array_push($params, $this->record->getVersionNumber()->getValue());
2333
                    }
2334
                }
2335
            }
2336
2337
            if ($this->record->isTableOverloaded()) {
2338
                $bindingsTypes .= 's';
2339
                array_push($params, get_class($this->record));
2340
            }
2341
2342
            // the ID may be on the WHERE clause for UPDATEs and DELETEs
2343
            if (!$this->record->isTransient()) {
2344
                $bindingsTypes .= 'i';
2345
                array_push($params, $this->record->getID());
2346
            }
2347
        }
2348
2349
        self::$logger->debug('bindingsTypes=['.$bindingsTypes.'], count: ['.mb_strlen($bindingsTypes).']');
2350
        self::$logger->debug('params ['.var_export($params, true).']');
2351
2352
        if ($params != null) {
2353
            $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...
2354
2355
            $count = count($params);
2356
2357
            for ($i = 0; $i < $count; ++$i) {
2358
                $bind_name = 'bind'.$i;
2359
                $$bind_name = $params[$i];
2360
                $bind_names[] = &$$bind_name;
2361
            }
2362
2363
            call_user_func_array(array($stmt, 'bind_param'), $bind_names);
2364
        }
2365
2366
        self::$logger->debug('<<bindParams ['.var_export($stmt, true).']');
2367
2368
        return $stmt;
2369
    }
2370
2371
    /**
2372
     * Dynamically binds the result of the supplied prepared statement to a 2d array, where each element in the array is another array
2373
     * representing a database row.
2374
     *
2375
     * @param \mysqli_stmt $stmt
2376
     *
2377
     * @return array A 2D array containing the query result.
2378
     *
2379
     * @since 1.1
2380
     */
2381
    private function bindResult($stmt)
2382
    {
2383
        $result = array();
2384
2385
        $metadata = $stmt->result_metadata();
2386
        $fields = $metadata->fetch_fields();
2387
2388
        while (true) {
2389
            $pointers = array();
2390
            $row = array();
2391
2392
            $pointers[] = $stmt;
2393
            foreach ($fields as $field) {
2394
                $fieldname = $field->name;
2395
                $pointers[] = &$row[$fieldname];
2396
            }
2397
2398
            call_user_func_array('mysqli_stmt_bind_result', $pointers);
2399
2400
            if (!$stmt->fetch()) {
2401
                break;
2402
            }
2403
2404
            $result[] = $row;
2405
        }
2406
2407
        $metadata->free();
2408
2409
        return $result;
2410
    }
2411
2412
    /**
2413
     * Parses a MySQL error for the value that violated a unique constraint.
2414
     *
2415
     * @param string $error The MySQL error string.
2416
     *
2417
     * @since 1.1
2418
     */
2419
    private function findOffendingValue($error)
2420
    {
2421
        self::$logger->debug('>>findOffendingValue(error=['.$error.'])');
2422
2423
        $singleQuote1 = mb_strpos($error, "'");
2424
        $singleQuote2 = mb_strrpos($error, "'");
2425
2426
        $value = mb_substr($error, $singleQuote1, ($singleQuote2-$singleQuote1)+1);
2427
        self::$logger->debug('<<findOffendingValue ['.$value.'])');
2428
2429
        return $value;
2430
    }
2431
2432
    /**
2433
     * (non-PHPdoc).
2434
     *
2435
     * @see Alpha\Model\ActiveRecordProviderInterface::checkDatabaseExists()
2436
     */
2437
    public static function checkDatabaseExists()
2438
    {
2439
        $config = ConfigProvider::getInstance();
2440
2441
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2442
2443
        $result = $connection->query('SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \''.$config->get('db.name').'\'');
2444
2445
        if (count($result) > 0) {
2446
            return true;
2447
        } else {
2448
            return false;
2449
        }
2450
    }
2451
2452
    /**
2453
     * (non-PHPdoc).
2454
     *
2455
     * @see Alpha\Model\ActiveRecordProviderInterface::createDatabase()
2456
     */
2457
    public static function createDatabase()
2458
    {
2459
        $config = ConfigProvider::getInstance();
2460
2461
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2462
2463
        $connection->query('CREATE DATABASE '.$config->get('db.name'));
2464
    }
2465
2466
    /**
2467
     * (non-PHPdoc).
2468
     *
2469
     * @see Alpha\Model\ActiveRecordProviderInterface::dropDatabase()
2470
     */
2471
    public static function dropDatabase()
2472
    {
2473
        $config = ConfigProvider::getInstance();
2474
2475
        $connection = new Mysqli($config->get('db.hostname'), $config->get('db.username'), $config->get('db.password'));
2476
2477
        $connection->query('DROP DATABASE '.$config->get('db.name'));
2478
    }
2479
2480
    /**
2481
     * (non-PHPdoc).
2482
     *
2483
     * @see Alpha\Model\ActiveRecordProviderInterface::backupDatabase()
2484
     */
2485
    public static function backupDatabase($targetFile)
2486
    {
2487
        $config = ConfigProvider::getInstance();
2488
2489
        exec('mysqldump  --host="'.$config->get('db.hostname').'" --user="'.$config->get('db.username').'" --password="'.$config->get('db.password').'" --opt '.$config->get('db.name').' 2>&1 >'.$targetFile);
2490
    }
2491
}
2492