ActiveRecordProviderMySQL::checkTableNeedsUpdate()   F
last analyzed

Complexity

Conditions 13
Paths 336

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 71
rs 3.8993
cc 13
nc 336
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Integer;
6
use Alpha\Model\Type\Timestamp;
7
use Alpha\Model\Type\DEnum;
8
use Alpha\Model\Type\Relation;
9
use Alpha\Model\Type\RelationLookup;
10
use Alpha\Model\Type\Double;
11
use Alpha\Model\Type\Text;
12
use Alpha\Model\Type\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