Completed
Push — develop ( 805531...35a4e1 )
by John
02:55
created

ActiveRecord::setID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Date;
6
use Alpha\Model\Type\Integer;
7
use Alpha\Model\Type\Timestamp;
8
use Alpha\Model\Type\TypeInterface;
9
use Alpha\Model\Type\Enum;
10
use Alpha\Model\Type\Relation;
11
use Alpha\Util\Config\ConfigProvider;
12
use Alpha\Util\Logging\Logger;
13
use Alpha\Util\Cache\CacheProviderFactory;
14
use Alpha\Util\Http\Session\SessionProviderFactory;
15
use Alpha\Exception\AlphaException;
16
use Alpha\Exception\FailedSaveException;
17
use Alpha\Exception\FailedDeleteException;
18
use Alpha\Exception\ValidationException;
19
use Alpha\Exception\RecordNotFoundException;
20
use Alpha\Exception\IllegalArguementException;
21
use Alpha\Exception\NotImplementedException;
22
use ReflectionClass;
23
use ReflectionProperty;
24
25
/**
26
 * Base active record class definition providing database storage via the configured provider.
27
 *
28
 * @since 1.0
29
 *
30
 * @author John Collins <[email protected]>
31
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
32
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
33
 * All rights reserved.
34
 *
35
 * <pre>
36
 * Redistribution and use in source and binary forms, with or
37
 * without modification, are permitted provided that the
38
 * following conditions are met:
39
 *
40
 * * Redistributions of source code must retain the above
41
 *   copyright notice, this list of conditions and the
42
 *   following disclaimer.
43
 * * Redistributions in binary form must reproduce the above
44
 *   copyright notice, this list of conditions and the
45
 *   following disclaimer in the documentation and/or other
46
 *   materials provided with the distribution.
47
 * * Neither the name of the Alpha Framework nor the names
48
 *   of its contributors may be used to endorse or promote
49
 *   products derived from this software without specific
50
 *   prior written permission.
51
 *
52
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
53
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
54
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
55
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
56
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
57
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
58
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
59
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
62
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
63
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65
 * </pre>
66
 */
67
abstract class ActiveRecord
68
{
69
    /**
70
     * The object ID.
71
     *
72
     * @var int
73
     *
74
     * @since 1.0
75
     */
76
    protected $ID;
77
78
    /**
79
     * The last database query run by this object.  Useful for tracing an error.
80
     *
81
     * @var string
82
     *
83
     * @since 1.0
84
     */
85
    protected $lastQuery;
86
87
    /**
88
     * The version number of the object, used for locking mechanism.
89
     *
90
     * @var \Alpha\Model\Type\Integer
91
     *
92
     * @since 1.0
93
     */
94
    protected $version_num;
95
96
    /**
97
     * The timestamp of creation.
98
     *
99
     * @var \Alpha\Model\Type\Timestamp
100
     *
101
     * @since 1.0
102
     */
103
    protected $created_ts;
104
105
    /**
106
     * The ID of the person who created this record.
107
     *
108
     * @var \Alpha\Model\Type\Integer
109
     *
110
     * @since 1.0
111
     */
112
    protected $created_by;
113
114
    /**
115
     * The timestamp of the last update.
116
     *
117
     * @var \Alpha\Model\Type\Timestamp
118
     *
119
     * @since 1.0
120
     */
121
    protected $updated_ts;
122
123
    /**
124
     * The ID of the person who last updated this record.
125
     *
126
     * @var \Alpha\Model\Type\Integer
127
     *
128
     * @since 1.0
129
     */
130
    protected $updated_by;
131
132
    /**
133
     * An array of the names of all of the default attributes of a persistent Record defined in this class.
134
     *
135
     * @var array
136
     *
137
     * @since 1.0
138
     */
139
    protected $defaultAttributes = array('ID', 'lastQuery', 'version_num', 'dataLabels', 'created_ts', 'created_by', 'updated_ts', 'updated_by', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
140
141
    /**
142
     * An array of the names of all of the transient attributes of a persistent Record which are not saved to the DB.
143
     *
144
     * @var array
145
     *
146
     * @since 1.0
147
     */
148
    protected $transientAttributes = array('lastQuery', 'dataLabels', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
149
150
    /**
151
     * An array of the uniquely-constained attributes of this persistent record.
152
     *
153
     * @var array
154
     *
155
     * @since 1.0
156
     */
157
    protected $uniqueAttributes = array();
158
159
    /**
160
     * An array of the data labels used for displaying class attributes.
161
     *
162
     * @var array
163
     *
164
     * @since 1.0
165
     */
166
    protected $dataLabels = array();
167
168
    /**
169
     * Trace logger.
170
     *
171
     * @var \Alpha\Util\Logging\Logger
172
     *
173
     * @since 1.0
174
     */
175
    private static $logger = null;
176
177
    /**
178
     * Determines if we will maintain a _history table for this record (default is false).
179
     *
180
     * @var bool
181
     *
182
     * @since 1.2
183
     */
184
    private $maintainHistory = false;
185
186
    /**
187
     * The constructor which sets up some housekeeping attributes.
188
     *
189
     * @since 1.0
190
     */
191
    public function __construct()
192
    {
193
        self::$logger = new Logger('ActiveRecord');
194
        self::$logger->debug('>>__construct()');
195
196
        $config = ConfigProvider::getInstance();
197
        $sessionProvider = $config->get('session.provider.name');
198
        $session = SessionProviderFactory::getInstance($sessionProvider);
199
200
        set_exception_handler('Alpha\Util\ErrorHandlers::catchException');
201
        set_error_handler('Alpha\Util\ErrorHandlers::catchError', $config->get('php.error.log.level'));
202
203
        $this->version_num = new Integer(0);
204
        $this->created_ts = new Timestamp(date('Y-m-d H:i:s'));
205
        $person_ID = ($session->get('currentUser') != null ? $session->get('currentUser')->getID() : 0);
206
        $this->created_by = new Integer($person_ID);
207
        $this->updated_ts = new Timestamp(date('Y-m-d H:i:s'));
208
        $this->updated_by = new Integer($person_ID);
209
210
        self::$logger->debug('<<__construct');
211
    }
212
213
    /**
214
     * Disconnects the current database connection if one exists.
215
     *
216
     * @since 1.0
217
     */
218
    public static function disconnect()
219
    {
220
        $config = ConfigProvider::getInstance();
221
222
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'));
223
        $provider->disconnect();
224
    }
225
226
    /**
227
     * Returns a 2d array, where each element in the array is another array representing a database row.
228
     *
229
     * @param string $sqlQuery
230
     *
231
     * @return array
232
     *
233
     * @since 1.1
234
     *
235
     * @throws \Alpha\Exception\CustomQueryException
236
     */
237
    public function query($sqlQuery)
238
    {
239
        self::$logger->debug('>>query(sqlQuery=['.$sqlQuery.'])');
240
241
        $config = ConfigProvider::getInstance();
242
243
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
244
        $result = $provider->query($sqlQuery);
245
246
        self::$logger->debug('<<query ['.print_r($result, true).']');
247
248
        return $result;
249
    }
250
251
    /**
252
     * Populates the child object with the properties retrived from the database for the object $ID.
253
     *
254
     * @param int $ID     The object ID of the business object to load.
255
     * @param int $version Optionaly, provide the version to load that version from the [tablename]_history table.
256
     *
257
     * @since 1.0
258
     *
259
     * @throws \Alpha\Exception\RecordNotFoundException
260
     */
261
    public function load($ID, $version = 0)
262
    {
263
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
264
265
        if (method_exists($this, 'before_load_callback')) {
266
            $this->{'before_load_callback'}();
267
        }
268
269
        $config = ConfigProvider::getInstance();
270
271
        $this->ID = $ID;
272
273
        if ($config->get('cache.provider.name') != '' && $this->loadFromCache()) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
274
            // Record was found in cache
275
        } else {
276
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
277
            $provider->load($ID, $version);
278
279
            if ($config->get('cache.provider.name') != '') {
280
                $this->addToCache();
281
            }
282
        }
283
284
        $this->setEnumOptions();
285
286
        if (method_exists($this, 'after_load_callback')) {
287
            $this->{'after_load_callback'}();
288
        }
289
290
        self::$logger->debug('<<load');
291
    }
292
293
    /**
294
     * Load all old versions (if any) of this record from the [tablename]_history table.
295
     *
296
     * @param int $ID The object ID of the record to load.
297
     *
298
     * @return array An array containing objects of this type of record object, order by version.
299
     *
300
     * @since 2.0
301
     *
302
     * @throws \Alpha\Exception\RecordFoundException
303
     */
304
    public function loadAllOldVersions($ID)
305
    {
306
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
307
308
        $config = ConfigProvider::getInstance();
309
310
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
311
        $objects = $provider->loadAllOldVersions($ID);
312
313
        self::$logger->debug('<<loadAllOldVersions['.count($objects).']');
314
315
        return $objects;
316
    }
317
318
    /**
319
     * Populates the child object from the database table by the given attribute value.
320
     *
321
     * @param string $attribute       The name of the attribute to load the object by.
322
     * @param string $value           The value of the attribute to load the object by.
323
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
324
     * @param array  $loadAttributes  The attributes to load from the database to this object (leave blank to load all attributes)
325
     *
326
     * @since 1.0
327
     *
328
     * @throws \Alpha\Exception\RecordNotFoundException
329
     */
330
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
331
    {
332
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
333
            loadAttributes=['.var_export($loadAttributes, true).'])');
334
335
        if (method_exists($this, 'before_loadByAttribute_callback')) {
336
            $this->{'before_loadByAttribute_callback'}();
337
        }
338
339
        $config = ConfigProvider::getInstance();
340
341
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
342
        $provider->loadByAttribute($attribute, $value, $ignoreClassType, $loadAttributes);
343
344
        $this->setEnumOptions();
345
346
        if ($config->get('cache.provider.name') != '' && count($loadAttributes) == 0) { // we will only cache fully-populated records
347
            $this->addToCache();
348
        }
349
350
        if (method_exists($this, 'after_loadByAttribute_callback')) {
351
            $this->{'after_loadByAttribute_callback'}();
352
        }
353
354
        self::$logger->debug('<<loadByAttribute');
355
    }
356
357
    /**
358
     * Loads all of the objects of this class into an array which is returned.
359
     *
360
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
361
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
362
     * @param string $orderBy         The name of the field to sort the objects by.
363
     * @param string $order           The order to sort the objects by.
364
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
365
     *
366
     * @return array An array containing objects of this type of business object.
367
     *
368
     * @since 1.0
369
     *
370
     * @throws \Alpha\Exception\RecordNotFoundException
371
     */
372
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
373
    {
374
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
375
376
        if (method_exists($this, 'before_loadAll_callback')) {
377
            $this->{'before_loadAll_callback'}();
378
        }
379
380
        $config = ConfigProvider::getInstance();
381
382
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
383
        $objects = $provider->loadAll($start, $limit, $orderBy, $order, $ignoreClassType);
384
385
        if (method_exists($this, 'after_loadAll_callback')) {
386
            $this->{'after_loadAll_callback'}();
387
        }
388
389
        self::$logger->debug('<<loadAll ['.count($objects).']');
390
391
        return $objects;
392
    }
393
394
    /**
395
     * Loads all of the objects of this class by the specified attribute into an array which is returned.
396
     *
397
     * @param string $attribute       The attribute to load the objects by.
398
     * @param string $value           The value of the attribute to load the objects by.
399
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
400
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
401
     * @param string $orderBy         The name of the field to sort the objects by.
402
     * @param string $order           The order to sort the objects by.
403
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
404
     * @param array  $constructorArgs An optional array of contructor arguements to pass to the records that will be generated and returned.  Supports a maximum of 5 arguements.
405
     *
406
     * @return array An array containing objects of this type of business object.
407
     *
408
     * @since 1.0
409
     *
410
     * @throws \Alpha\Exception\RecordNotFoundException
411
     * @throws \Alpha\Exception\IllegalArguementException
412
     */
413
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
414
    {
415
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
416
417
        if (method_exists($this, 'before_loadAllByAttribute_callback')) {
418
            $this->{'before_loadAllByAttribute_callback'}();
419
        }
420
421
        $config = ConfigProvider::getInstance();
422
423
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
424
        $objects = $provider->loadAllByAttribute($attribute, $value, $start, $limit, $orderBy, $order, $ignoreClassType);
425
426
        if (method_exists($this, 'after_loadAllByAttribute_callback')) {
427
            $this->{'after_loadAllByAttribute_callback'}();
428
        }
429
430
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
431
432
        return $objects;
433
    }
434
435
    /**
436
     * Loads all of the objects of this class by the specified attributes into an array which is returned.
437
     *
438
     * @param array  $attributes      The attributes to load the objects by.
439
     * @param array  $values          The values of the attributes to load the objects by.
440
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
441
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
442
     * @param string $orderBy         The name of the field to sort the objects by.
443
     * @param string $order           The order to sort the objects by.
444
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
445
     *
446
     * @return array An array containing objects of this type of business object.
447
     *
448
     * @since 1.0
449
     *
450
     * @throws \Alpha\Exception\RecordNotFoundException
451
     * @throws \Alpha\Exception\IllegalArguementException
452
     */
453
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
454
    {
455
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
456
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
457
458
        if (method_exists($this, 'before_loadAllByAttributes_callback')) {
459
            $this->{'before_loadAllByAttributes_callback'}();
460
        }
461
462
        $config = ConfigProvider::getInstance();
463
464
        if (!is_array($attributes) || !is_array($values)) {
465
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).
466
                '] provided to loadAllByAttributes');
467
        }
468
469
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
470
        $objects = $provider->loadAllByAttributes($attributes, $values, $start, $limit, $orderBy, $order, $ignoreClassType);
471
472
        if (method_exists($this, 'after_loadAllByAttributes_callback')) {
473
            $this->{'after_loadAllByAttributes_callback'}();
474
        }
475
476
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
477
478
        return $objects;
479
    }
480
481
    /**
482
     * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
483
     *
484
     * @param string $date            The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
485
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
486
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
487
     * @param string $orderBy         The name of the field to sort the objects by.
488
     * @param string $order           The order to sort the objects by.
489
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
490
     *
491
     * @return array An array containing objects of this type of business object.
492
     *
493
     * @since 1.0
494
     *
495
     * @throws \Alpha\Exception\RecordNotFoundException
496
     */
497
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
498
    {
499
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
500
501
        if (method_exists($this, 'before_loadAllByDayUpdated_callback')) {
502
            $this->{'before_loadAllByDayUpdated_callback'}();
503
        }
504
505
        $config = ConfigProvider::getInstance();
506
507
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
508
        $objects = $provider->loadAllByDayUpdated($date, $start, $limit, $orderBy, $order, $ignoreClassType);
509
510
        if (method_exists($this, 'after_loadAllByDayUpdated_callback')) {
511
            $this->{'after_loadAllByDayUpdated_callback'}();
512
        }
513
514
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
515
516
        return $objects;
517
    }
518
519
    /**
520
     * Loads all of the specified attribute values of this class by the specified attribute into an
521
     * array which is returned.
522
     *
523
     * @param string $attribute       The attribute name to load the field values by.
524
     * @param string $value           The value of the attribute to load the field values by.
525
     * @param string $returnAttribute The name of the attribute to return.
526
     * @param string $order           The order to sort the records by.
527
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
528
     *
529
     * @return array An array of field values.
530
     *
531
     * @since 1.0
532
     *
533
     * @throws \Alpha\Exception\RecordNotFoundException
534
     */
535
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
536
    {
537
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
538
539
        $config = ConfigProvider::getInstance();
540
541
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
542
        $values = $provider->loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order, $ignoreClassType);
543
544
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
545
546
        return $values;
547
    }
548
549
    /**
550
     * Saves the object.  If $this->ID is empty or null it will INSERT, otherwise UPDATE.
551
     *
552
     * @since 1.0
553
     *
554
     * @throws \Alpha\Exception\FailedSaveException
555
     * @throws \Alpha\Exception\LockingException
556
     * @throws \Alpha\Exception\ValidationException
557
     */
558
    public function save()
559
    {
560
        self::$logger->debug('>>save()');
561
562
        if (method_exists($this, 'before_save_callback')) {
563
            $this->{'before_save_callback'}();
564
        }
565
566
        $config = ConfigProvider::getInstance();
567
568
        // firstly we will validate the object before we try to save it
569
        $this->validate();
570
571
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
572
        $provider->save();
573
574
        if ($config->get('cache.provider.name') != '') {
575
            $this->removeFromCache();
576
            $this->addToCache();
577
        }
578
579
        if (method_exists($this, 'after_save_callback')) {
580
            $this->{'after_save_callback'}();
581
        }
582
    }
583
584
    /**
585
     * Saves the field specified with the value supplied.  Only works for persistent records.  Note that no Alpha type
586
     * validation is performed with this method!
587
     *
588
     * @param string $attribute The name of the attribute to save.
589
     * @param mixed  $value     The value of the attribute to save.
590
     *
591
     * @since 1.0
592
     *
593
     * @throws \Alpha\Exception\IllegalArguementException
594
     * @throws \Alpha\Exception\FailedSaveException
595
     */
596
    public function saveAttribute($attribute, $value)
597
    {
598
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
599
600
        if (method_exists($this, 'before_saveAttribute_callback')) {
601
            $this->{'before_saveAttribute_callback'}();
602
        }
603
604
        $config = ConfigProvider::getInstance();
605
606
        if (!isset($this->$attribute)) {
607
            throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
608
        }
609
610
        if ($this->isTransient()) {
611
            throw new FailedSaveException('Cannot perform saveAttribute method on transient record!');
612
        }
613
614
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
615
        $provider->saveAttribute($attribute, $value);
616
617
        if ($config->get('cache.provider.name') != '') {
618
            $this->removeFromCache();
619
            $this->addToCache();
620
        }
621
622
        if (method_exists($this, 'after_saveAttribute_callback')) {
623
            $this->{'after_saveAttribute_callback'}();
624
        }
625
626
        self::$logger->debug('<<saveAttribute');
627
    }
628
629
    /**
630
     * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
631
     *
632
     * @since 1.2
633
     *
634
     * @throws \Alpha\Exception\FailedSaveException
635
     */
636
    public function saveHistory()
637
    {
638
        self::$logger->debug('>>saveHistory()');
639
640
        if (method_exists($this, 'before_saveHistory_callback')) {
641
            $this->{'before_saveHistory_callback'}();
642
        }
643
644
        $config = ConfigProvider::getInstance();
645
646
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
647
        $provider->saveHistory();
648
649
        if (method_exists($this, 'after_saveHistory_callback')) {
650
            $this->{'after_saveHistory_callback'}();
651
        }
652
    }
653
654
    /**
655
     * Validates the object to be saved.
656
     *
657
     * @since 1.0
658
     *
659
     * @throws \Alpha\Exception\ValidationException
660
     */
661
    protected function validate()
662
    {
663
        self::$logger->debug('>>validate()');
664
665
        if (method_exists($this, 'before_validate_callback')) {
666
            $this->{'before_validate_callback'}();
667
        }
668
669
        // get the class attributes
670
        $reflection = new ReflectionClass(get_class($this));
671
        $properties = $reflection->getProperties();
672
673
        foreach ($properties as $propObj) {
674
            $propName = $propObj->name;
675
            if (!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
676
                $propClass = new ReflectionClass($this->getPropObject($propName));
677
                $propClass = $propClass->getShortname();
678
                if (mb_strtoupper($propClass) != 'ENUM' &&
679
                mb_strtoupper($propClass) != 'DENUM' &&
680
                mb_strtoupper($propClass) != 'DENUMITEM' &&
681
                mb_strtoupper($propClass) != 'BOOLEAN') {
682
                    if ($this->getPropObject($propName) != false && !preg_match($this->getPropObject($propName)->getRule(), $this->getPropObject($propName)->getValue())) {
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 getRule() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Date, Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text, Alpha\Model\Type\Timestamp. 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...
683
                        self::$logger->debug('<<validate');
684
                        throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
685
                    }
686
                }
687
            }
688
        }
689
690
        if (method_exists($this, 'after_validate_callback')) {
691
            $this->{'after_validate_callback'}();
692
        }
693
694
        self::$logger->debug('<<validate');
695
    }
696
697
    /**
698
     * Deletes the current object from the database.
699
     *
700
     * @since 1.0
701
     *
702
     * @throws \Alpha\Exception\FailedDeleteException
703
     */
704
    public function delete()
705
    {
706
        self::$logger->debug('>>delete()');
707
708
        if (method_exists($this, 'before_delete_callback')) {
709
            $this->{'before_delete_callback'}();
710
        }
711
712
        $config = ConfigProvider::getInstance();
713
714
        // get the class attributes
715
        $reflection = new ReflectionClass(get_class($this));
716
        $properties = $reflection->getProperties();
717
718
        // check for any relations on this object, then remove them to prevent orphaned data
719
        foreach ($properties as $propObj) {
720
            $propName = $propObj->name;
721
722
            if (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
723
                $prop = $this->getPropObject($propName);
724
725
                // Handle MANY-TO-MANY rels
726
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
727
                    self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
728
729
                    try {
730
                        // check to see if the rel is on this class
731
                        $side = $prop->getSide(get_class($this));
732
                    } catch (IllegalArguementException $iae) {
733
                        $side = $prop->getSide(get_parent_class($this));
734
                    }
735
736
                    self::$logger->debug('Side is ['.$side.']'.$this->getID());
737
738
                    $lookUp = $prop->getLookup();
739
                    self::$logger->debug('Lookup object['.var_export($lookUp, true).']');
740
741
                    // delete all of the old RelationLookup objects for this rel
742
                    if ($side == 'left') {
743
                        $lookUp->deleteAllByAttribute('leftID', $this->getID());
744
                    } else {
745
                        $lookUp->deleteAllByAttribute('rightID', $this->getID());
746
                    }
747
                    self::$logger->debug('...done deleting!');
748
                }
749
750
                // should set related field values to null (MySQL is doing this for us as-is)
751
                if ($prop->getRelationType() == 'ONE-TO-MANY' && !$prop->getRelatedClass() == 'Alpha\Model\Tag') {
752
                    $relatedObjects = $prop->getRelatedObjects();
753
754
                    foreach ($relatedObjects as $object) {
755
                        $object->set($prop->getRelatedClassField(), null);
756
                        $object->save();
757
                    }
758
                }
759
760
                // in the case of tags, we will always remove the related tags once the Record is deleted
761
                if ($prop->getRelationType() == 'ONE-TO-MANY' && $prop->getRelatedClass() == 'Alpha\Model\Tag') {
762
                    // just making sure that the Relation is set to current ID as its transient
763
                    $prop->setValue($this->getID());
764
                    $relatedObjects = $prop->getRelatedObjects();
765
766
                    foreach ($relatedObjects as $object) {
767
                        $object->delete();
768
                    }
769
                }
770
            }
771
        }
772
773
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
774
        $provider->delete();
775
776
        if ($config->get('cache.provider.name') != '') {
777
            $this->removeFromCache();
778
        }
779
780
        if (method_exists($this, 'after_delete_callback')) {
781
            $this->{'after_delete_callback'}();
782
        }
783
784
        $this->clear();
785
        self::$logger->debug('<<delete');
786
    }
787
788
    /**
789
     * Delete all object instances from the database by the specified attribute matching the value provided.
790
     *
791
     * @param string $attribute The name of the field to delete the objects by.
792
     * @param mixed  $value     The value of the field to delete the objects by.
793
     *
794
     * @return int The number of rows deleted.
795
     *
796
     * @since 1.0
797
     *
798
     * @throws \Alpha\Exception\FailedDeleteException
799
     */
800
    public function deleteAllByAttribute($attribute, $value)
801
    {
802
        self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
803
804
        if (method_exists($this, 'before_deleteAllByAttribute_callback')) {
805
            $this->{'before_deleteAllByAttribute_callback'}();
806
        }
807
808
        try {
809
            $doomedObjects = $this->loadAllByAttribute($attribute, $value);
810
            $deletedRowCount = 0;
811
812
            foreach ($doomedObjects as $object) {
813
                $object->delete();
814
                ++$deletedRowCount;
815
            }
816
        } catch (RecordNotFoundException $bonf) {
817
            // nothing found to delete
818
            self::$logger->warn($bonf->getMessage());
819
820
            return 0;
821
        } catch (AlphaException $e) {
822
            self::$logger->debug('<<deleteAllByAttribute [0]');
823
            throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
824
        }
825
826
        if (method_exists($this, 'after_deleteAllByAttribute_callback')) {
827
            $this->{'after_deleteAllByAttribute_callback'}();
828
        }
829
830
        self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
831
832
        return $deletedRowCount;
833
    }
834
835
    /**
836
     * Gets the version_num of the object from the database (returns 0 if the Record is not saved yet).
837
     *
838
     * @return int
839
     *
840
     * @since 1.0
841
     *
842
     * @throws \Alpha\Exception\RecordNotFoundException
843
     */
844
    public function getVersion()
845
    {
846
        self::$logger->debug('>>getVersion()');
847
848
        if (method_exists($this, 'before_getVersion_callback')) {
849
            $this->{'before_getVersion_callback'}();
850
        }
851
852
        $config = ConfigProvider::getInstance();
853
854
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
855
        $ver = $provider->getVersion();
856
857
        if (method_exists($this, 'after_getVersion_callback')) {
858
            $this->{'after_getVersion_callback'}();
859
        }
860
861
        self::$logger->debug('<<getVersion ['.$ver.']');
862
863
        return $ver;
864
    }
865
866
    /**
867
     * Builds a new database table for the Record class.
868
     *
869
     * @since 1.0
870
     *
871
     * @throws \Alpha\Exception\AlphaException
872
     */
873
    public function makeTable()
874
    {
875
        self::$logger->debug('>>makeTable()');
876
877
        if (method_exists($this, 'before_makeTable_callback')) {
878
            $this->{'before_makeTable_callback'}();
879
        }
880
881
        $config = ConfigProvider::getInstance();
882
883
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
884
        $provider->makeTable();
885
886
        if (method_exists($this, 'after_makeTable_callback')) {
887
            $this->{'after_makeTable_callback'}();
888
        }
889
890
        self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
891
892
        self::$logger->debug('<<makeTable');
893
    }
894
895
    /**
896
     * Builds a new database table for the Record class to story it's history of changes.
897
     *
898
     * @since 1.2
899
     *
900
     * @throws \Alpha\Exception\AlphaException
901
     */
902
    public function makeHistoryTable()
903
    {
904
        self::$logger->debug('>>makeHistoryTable()');
905
906
        if (method_exists($this, 'before_makeHistoryTable_callback')) {
907
            $this->{'before_makeHistoryTable_callback'}();
908
        }
909
910
        $config = ConfigProvider::getInstance();
911
912
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
913
        $provider->makeHistoryTable();
914
915
        if (method_exists($this, 'after_makeHistoryTable_callback')) {
916
            $this->{'after_makeHistoryTable_callback'}();
917
        }
918
919
        self::$logger->info('Successfully created the table ['.$this->getTableName().'_history] for the class ['.get_class($this).']');
920
921
        self::$logger->debug('<<makeHistoryTable');
922
    }
923
924
    /**
925
     * Re-builds the table if the model requirements have changed.  All data is lost!
926
     *
927
     * @since 1.0
928
     *
929
     * @throws \Alpha\Exception\AlphaException
930
     */
931
    public function rebuildTable()
932
    {
933
        self::$logger->debug('>>rebuildTable()');
934
935
        if (method_exists($this, 'before_rebuildTable_callback')) {
936
            $this->{'before_rebuildTable_callback'}();
937
        }
938
939
        $config = ConfigProvider::getInstance();
940
941
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
942
        $provider->rebuildTable();
943
944
        if (method_exists($this, 'after_rebuildTable_callback')) {
945
            $this->{'after_rebuildTable_callback'}();
946
        }
947
948
        self::$logger->debug('<<rebuildTable');
949
    }
950
951
    /**
952
     * Drops the table if the model requirements have changed.  All data is lost!
953
     *
954
     * @since 1.0
955
     *
956
     * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
957
     *
958
     * @throws \Alpha\Exception\AlphaException
959
     */
960
    public function dropTable($tableName = null)
961
    {
962
        self::$logger->debug('>>dropTable()');
963
964
        if (method_exists($this, 'before_dropTable_callback')) {
965
            $this->{'before_dropTable_callback'}();
966
        }
967
968
        $config = ConfigProvider::getInstance();
969
970
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
971
        $provider->dropTable($tableName);
972
973
        if (method_exists($this, 'after_dropTable_callback')) {
974
            $this->{'after_dropTable_callback'}();
975
        }
976
977
        self::$logger->debug('<<dropTable');
978
    }
979
980
    /**
981
     * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
982
     * database).
983
     *
984
     * @param string $propName The name of the new field to add to the database table.
985
     *
986
     * @since 1.0
987
     *
988
     * @throws \Alpha\Exception\AlphaException
989
     */
990
    public function addProperty($propName)
991
    {
992
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
993
994
        $config = ConfigProvider::getInstance();
995
996
        if (method_exists($this, 'before_addProperty_callback')) {
997
            $this->{'before_addProperty_callback'}();
998
        }
999
1000
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1001
        $provider->addProperty($propName);
1002
1003
        if (method_exists($this, 'after_addProperty_callback')) {
1004
            $this->{'after_addProperty_callback'}();
1005
        }
1006
1007
        self::$logger->debug('<<addProperty');
1008
    }
1009
1010
    /**
1011
     * Populates the current business object from the provided hash array.
1012
     *
1013
     * @param array $hashArray
1014
     *
1015
     * @since 1.2.1
1016
     */
1017
    public function populateFromArray($hashArray)
1018
    {
1019
        self::$logger->debug('>>populateFromArray(hashArray=['.print_r($hashArray, true).'])');
1020
1021
        // get the class attributes
1022
        $reflection = new ReflectionClass(get_class($this));
1023
        $properties = $reflection->getProperties();
1024
1025
        foreach ($properties as $propObj) {
1026
            $propName = $propObj->name;
1027
1028
            if (isset($hashArray[$propName])) {
1029
                if ($this->getPropObject($propName) instanceof Date || $this->getPropObject($propName) instanceof Timestamp) {
1030
                    $this->getPropObject($propName)->populateFromString($hashArray[$propName]);
1031
                } elseif ($this->getPropObject($propName) instanceof TypeInterface) {
1032
                    $this->getPropObject($propName)->setValue($hashArray[$propName]);
1033
                }
1034
1035
                if ($propName == 'version_num' && isset($hashArray['version_num'])) {
1036
                    $this->version_num->setValue($hashArray['version_num']);
1037
                }
1038
1039
                if ($this->getPropObject($propName) instanceof Relation) {
1040
                    $rel = $this->getPropObject($propName);
1041
1042
                    if ($rel->getRelationType() == 'MANY-TO-MANY') {
1043
                        $IDs = explode(',', $hashArray[$propName]);
1044
                        $rel->setRelatedIDs($IDs);
1045
                        $this->$propName = $rel;
1046
                    }
1047
                }
1048
            }
1049
        }
1050
1051
        self::$logger->debug('<<populateFromArray');
1052
    }
1053
1054
    /**
1055
     * Gets the maximum ID value from the database for this class type.
1056
     *
1057
     * @return int The maximum ID value in the class table.
1058
     *
1059
     * @since 1.0
1060
     *
1061
     * @throws \Alpha\Exception\AlphaException
1062
     */
1063
    public function getMAX()
1064
    {
1065
        self::$logger->debug('>>getMAX()');
1066
1067
        if (method_exists($this, 'before_getMAX_callback')) {
1068
            $this->{'before_getMAX_callback'}();
1069
        }
1070
1071
        $config = ConfigProvider::getInstance();
1072
1073
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1074
        $max = $provider->getMAX();
1075
1076
        if (method_exists($this, 'after_getMAX_callback')) {
1077
            $this->{'after_getMAX_callback'}();
1078
        }
1079
1080
        self::$logger->debug('<<getMAX ['.$max.']');
1081
1082
        return $max;
1083
    }
1084
1085
    /**
1086
     * Gets the count from the database for the amount of objects of this class.
1087
     *
1088
     * @param array $attributes The attributes to count the objects by (optional).
1089
     * @param array $values     The values of the attributes to count the objects by (optional).
1090
     *
1091
     * @return int
1092
     *
1093
     * @since 1.0
1094
     *
1095
     * @throws \Alpha\Exception\AlphaException
1096
     * @throws \Alpha\Exception\IllegalArguementException
1097
     */
1098
    public function getCount($attributes = array(), $values = array())
1099
    {
1100
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1101
1102
        if (method_exists($this, 'before_getCount_callback')) {
1103
            $this->{'before_getCount_callback'}();
1104
        }
1105
1106
        $config = ConfigProvider::getInstance();
1107
1108
        if (!is_array($attributes) || !is_array($values)) {
1109
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
1110
        }
1111
1112
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1113
        $count = $provider->getCount($attributes, $values);
1114
1115
        if (method_exists($this, 'after_getCount_callback')) {
1116
            $this->{'after_getCount_callback'}();
1117
        }
1118
1119
        self::$logger->debug('<<getCount ['.$count.']');
1120
1121
        return $count;
1122
    }
1123
1124
    /**
1125
     * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
1126
     * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
1127
     *
1128
     * @return int
1129
     *
1130
     * @since 1.2
1131
     *
1132
     * @throws \Alpha\Exception\AlphaException
1133
     */
1134
    public function getHistoryCount()
1135
    {
1136
        self::$logger->debug('>>getHistoryCount()');
1137
1138
        if (method_exists($this, 'before_getHistoryCount_callback')) {
1139
            $this->{'before_getHistoryCount_callback'}();
1140
        }
1141
1142
        $config = ConfigProvider::getInstance();
1143
1144
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1145
        $count = $provider->getHistoryCount();
1146
1147
        if (method_exists($this, 'after_getHistoryCount_callback')) {
1148
            $this->{'after_getHistoryCount_callback'}();
1149
        }
1150
1151
        self::$logger->debug('<<getHistoryCount ['.$count.']');
1152
1153
        return $count;
1154
    }
1155
1156
    /**
1157
     * Gets the ID for the object in zero-padded format (same as getID()).
1158
     *
1159
     * @return int 11 digit zero-padded ID value.
1160
     *
1161
     * @since 1.0
1162
     */
1163
    final public function getID()
1164
    {
1165
        if (self::$logger == null) {
1166
            self::$logger = new Logger('ActiveRecord');
1167
        }
1168
        self::$logger->debug('>>getID()');
1169
        $oid = str_pad($this->ID, 11, '0', STR_PAD_LEFT);
1170
        self::$logger->debug('<<getID ['.$oid.']');
1171
1172
        return $oid;
1173
    }
1174
1175
    /**
1176
     * Method for getting version number of the object.
1177
     *
1178
     * @return \Alpha\Model\Type\Integer The object version number.
1179
     *
1180
     * @since 1.0
1181
     */
1182
    public function getVersionNumber()
1183
    {
1184
        self::$logger->debug('>>getVersionNumber()');
1185
        self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
1186
1187
        return $this->version_num;
1188
    }
1189
1190
    /**
1191
     * Populate all of the enum options for this object from the database.
1192
     *
1193
     * @since 1.0
1194
     *
1195
     * @throws \Alpha\Exception\AlphaException
1196
     */
1197
    protected function setEnumOptions()
1198
    {
1199
        self::$logger->debug('>>setEnumOptions()');
1200
1201
        if (method_exists($this, 'before_setEnumOptions_callback')) {
1202
            $this->{'before_setEnumOptions_callback'}();
1203
        }
1204
1205
        $config = ConfigProvider::getInstance();
1206
1207
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1208
        try {
1209
            $provider->setEnumOptions();
1210
        } catch (NotImplementedException $e) {
1211
            self::$logger->debug($e->getMessage());
1212
        }
1213
1214
        self::$logger->debug('<<setEnumOptions');
1215
    }
1216
1217
    /**
1218
     * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
1219
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
1220
     * get.ucfirst($prop) method even if it exists, false otherwise (default).
1221
     *
1222
     * @param string $prop           The name of the object property to get.
1223
     * @param bool   $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
1224
     *
1225
     * @return mixed The property value.
1226
     *
1227
     * @since 1.0
1228
     *
1229
     * @throws \Alpha\Exception\IllegalArguementException
1230
     * @throws \Alpha\Exception\AlphaException
1231
     */
1232
    public function get($prop, $noChildMethods = false)
1233
    {
1234
        if (self::$logger == null) {
1235
            self::$logger = new Logger('ActiveRecord');
1236
        }
1237
1238
        self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
1239
1240
        if (method_exists($this, 'before_get_callback')) {
1241
            $this->{'before_get_callback'}();
1242
        }
1243
1244
        if (empty($prop)) {
1245
            throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
1246
        }
1247
1248
        // handle attributes with a get.ucfirst($prop) method
1249
        if (!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
1250
            if (method_exists($this, 'after_get_callback')) {
1251
                $this->{'after_get_callback'}();
1252
            }
1253
1254
            $methodName = 'get'.ucfirst($prop);
1255
1256
            self::$logger->debug('<<get ['.print_r($this->$methodName(), true).'])');
1257
1258
            return $this->$methodName();
1259
        } else {
1260
            // handle attributes with no dedicated child get.ucfirst($prop) method
1261
            if (isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
1262
                if (method_exists($this, 'after_get_callback')) {
1263
                    $this->{'after_get_callback'}();
1264
                }
1265
1266
                // complex types will have a getValue() method, return the value of that
1267
                self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
1268
1269
                return $this->$prop->getValue();
1270
            } elseif (isset($this->$prop)) {
1271
                if (method_exists($this, 'after_get_callback')) {
1272
                    $this->{'after_get_callback'}();
1273
                }
1274
1275
                // simple types returned as-is
1276
                self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
1277
1278
                return $this->$prop;
1279
            } else {
1280
                self::$logger->debug('<<get');
1281
                throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
1282
            }
1283
        }
1284
    }
1285
1286
    /**
1287
     * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
1288
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
1289
     * any get.ucfirst($prop) method even if it exists, false otherwise (default).
1290
     *
1291
     * @param string $prop           The name of the property to set.
1292
     * @param mixed  $value          The value of the property to set.
1293
     * @param bool   $noChildMethods Set to true if you do not want to use setters in the child object, defaults to false.
1294
     *
1295
     * @since 1.0
1296
     *
1297
     * @throws \Alpha\Exception\AlphaException
1298
     */
1299
    public function set($prop, $value, $noChildMethods = false)
1300
    {
1301
        self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
1302
1303
        if (method_exists($this, 'before_set_callback')) {
1304
            $this->{'before_set_callback'}();
1305
        }
1306
1307
        // handle attributes with a set.ucfirst($prop) method
1308
        if (!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
1309
            if (method_exists($this, 'after_set_callback')) {
1310
                $this->{'after_set_callback'}();
1311
            }
1312
1313
            $methodName = 'set'.ucfirst($prop);
1314
1315
            $this->$methodName($value);
1316
        } else {
1317
            // handle attributes with no dedicated child set.ucfirst($prop) method
1318
            if (isset($this->$prop)) {
1319
                if (method_exists($this, 'after_set_callback')) {
1320
                    $this->{'after_set_callback'}();
1321
                }
1322
1323
                // complex types will have a setValue() method to call
1324
                if (is_object($this->$prop) && get_class($this->$prop) !== false) {
1325
                    if (mb_strtoupper(get_class($this->$prop)) != 'DATE' && mb_strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
1326
                        $this->$prop->setValue($value);
1327
                    } else {
1328
                        // Date and Timestamp objects have a special setter accepting a string
1329
                        $this->$prop->populateFromString($value);
1330
                    }
1331
                } else {
1332
                    // simple types set directly
1333
                    $this->$prop = $value;
1334
                }
1335
            } else {
1336
                throw new AlphaException('Could not set the property ['.$prop.'] on the object of the class ['.get_class($this).'].  Property may not exist, or else does not have a setValue() method and is private or protected.');
1337
            }
1338
        }
1339
        self::$logger->debug('<<set');
1340
    }
1341
1342
    /**
1343
     * Gets the property object rather than the value for complex attributes.  Returns false if
1344
     * the property exists but is private.
1345
     *
1346
     * @param string $prop The name of the property we are getting.
1347
     *
1348
     * @return \Alpha\Model\Type\Type|bool The complex type object found.
1349
     *
1350
     * @since 1.0
1351
     *
1352
     * @throws \Alpha\Exception\IllegalArguementException
1353
     */
1354
    public function getPropObject($prop)
1355
    {
1356
        self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
1357
1358
        if (method_exists($this, 'before_getPropObject_callback')) {
1359
            $this->{'before_getPropObject_callback'}();
1360
        }
1361
1362
        // get the class attributes
1363
        $reflection = new \ReflectionObject($this);
1364
        $properties = $reflection->getProperties();
1365
1366
        // firstly, check for private
1367
        $attribute = new ReflectionProperty($this, $prop);
1368
1369
        if ($attribute->isPrivate()) {
1370
            if (method_exists($this, 'after_getPropObject_callback')) {
1371
                $this->{'after_getPropObject_callback'}();
1372
            }
1373
1374
            self::$logger->debug('<<getPropObject [false]');
1375
1376
            return false;
1377
        }
1378
1379
        foreach ($properties as $propObj) {
1380
            $propName = $propObj->name;
1381
1382
            if ($prop == $propName) {
1383
                if (method_exists($this, 'after_getPropObject_callback')) {
1384
                    $this->{'after_getPropObject_callback'}();
1385
                }
1386
1387
                self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
1388
1389
                return $this->$prop;
1390
            }
1391
        }
1392
1393
        self::$logger->debug('<<getPropObject');
1394
        throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
1395
    }
1396
1397
    /**
1398
     * Checks to see if the table exists in the database for the current business class.
1399
     *
1400
     * @param bool $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1401
     *
1402
     * @return bool
1403
     *
1404
     * @since 1.0
1405
     *
1406
     * @throws \Alpha\Exception\AlphaException
1407
     */
1408
    public function checkTableExists($checkHistoryTable = false)
1409
    {
1410
        self::$logger->debug('>>checkTableExists()');
1411
1412
        if (method_exists($this, 'before_checkTableExists_callback')) {
1413
            $this->{'before_checkTableExists_callback'}();
1414
        }
1415
1416
        $config = ConfigProvider::getInstance();
1417
1418
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1419
        $tableExists = $provider->checkTableExists($checkHistoryTable);
1420
1421
        if (method_exists($this, 'after_checkTableExists_callback')) {
1422
            $this->{'after_checkTableExists_callback'}();
1423
        }
1424
1425
        self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1426
1427
        return $tableExists;
1428
    }
1429
1430
    /**
1431
     * Static method to check the database and see if the table for the indicated Record class name
1432
     * exists (assumes table name will be $RecordClassName less "Object").
1433
     *
1434
     * @param string $RecordClassName       The name of the business object class we are checking.
1435
     * @param bool   $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1436
     *
1437
     * @return bool
1438
     *
1439
     * @since 1.0
1440
     *
1441
     * @throws \Alpha\Exception\AlphaException
1442
     */
1443
    public static function checkRecordTableExists($RecordClassName, $checkHistoryTable = false)
1444
    {
1445
        if (self::$logger == null) {
1446
            self::$logger = new Logger('ActiveRecord');
1447
        }
1448
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$RecordClassName.'])');
1449
1450
        $config = ConfigProvider::getInstance();
1451
1452
        $provider = $config->get('db.provider.name');
1453
1454
        $tableExists = $provider::checkRecordTableExists($RecordClassName, $checkHistoryTable);
1455
1456
        self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1457
1458
        return $tableExists;
1459
    }
1460
1461
    /**
1462
     * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
1463
     * database table is in sync with the class definition.
1464
     *
1465
     * @return bool
1466
     *
1467
     * @since 1.0
1468
     *
1469
     * @throws \Alpha\Exception\AlphaException
1470
     */
1471
    public function checkTableNeedsUpdate()
1472
    {
1473
        self::$logger->debug('>>checkTableNeedsUpdate()');
1474
1475
        $config = ConfigProvider::getInstance();
1476
1477
        if (method_exists($this, 'before_checkTableNeedsUpdate_callback')) {
1478
            $this->{'before_checkTableNeedsUpdate_callback'}();
1479
        }
1480
1481
        $tableExists = $this->checkTableExists();
1482
1483
        if (!$tableExists) {
1484
            self::$logger->debug('<<checkTableNeedsUpdate [true]');
1485
1486
            return true;
1487
        } else {
1488
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1489
            $updateRequired = $provider->checkTableNeedsUpdate();
1490
1491
            if (method_exists($this, 'after_checkTableNeedsUpdate_callback')) {
1492
                $this->{'after_checkTableNeedsUpdate_callback'}();
1493
            }
1494
1495
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1496
1497
            return $updateRequired;
1498
        }
1499
    }
1500
1501
    /**
1502
     * Returns an array containing any properties on the class which have not been created on the database
1503
     * table yet.
1504
     *
1505
     * @return array An array of missing fields in the database table.
1506
     *
1507
     * @since 1.0
1508
     *
1509
     * @throws \Alpha\Exception\AlphaException
1510
     */
1511
    public function findMissingFields()
1512
    {
1513
        self::$logger->debug('>>findMissingFields()');
1514
1515
        $config = ConfigProvider::getInstance();
1516
1517
        if (method_exists($this, 'before_findMissingFields_callback')) {
1518
            $this->{'before_findMissingFields_callback'}();
1519
        }
1520
1521
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1522
        $missingFields = $provider->findMissingFields();
1523
1524
        if (method_exists($this, 'after_findMissingFields_callback')) {
1525
            $this->{'after_findMissingFields_callback'}();
1526
        }
1527
1528
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1529
1530
        return $missingFields;
1531
    }
1532
1533
    /**
1534
     * Getter for the TABLE_NAME, which should be set by a child of this class.
1535
     *
1536
     * @return string The table name in the database.
1537
     *
1538
     * @since 1.0
1539
     *
1540
     * @throws \Alpha\Exception\AlphaException
1541
     */
1542
    public function getTableName()
1543
    {
1544
        self::$logger->debug('>>getTableName()');
1545
1546
        $className = get_class($this);
1547
1548
        $tableName = $className::TABLE_NAME;
1549
1550
        if (!empty($tableName)) {
1551
            self::$logger->debug('<<getTableName ['.$tableName.']');
1552
1553
            return $tableName;
1554
        } else {
1555
            throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
1556
        }
1557
    }
1558
1559
    /**
1560
     * Method for getting the ID of the person who created this record.
1561
     *
1562
     * @return \Alpha\Model\Type\Integer The ID of the creator.
1563
     *
1564
     * @since 1.0
1565
     */
1566
    public function getCreatorId()
1567
    {
1568
        self::$logger->debug('>>getCreatorId()');
1569
        self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
1570
1571
        return $this->created_by;
1572
    }
1573
1574
    /**
1575
     * Method for getting the ID of the person who updated this record.
1576
     *
1577
     * @return \Alpha\Model\Type\Integer The ID of the updator.
1578
     *
1579
     * @since 1.0
1580
     */
1581
    public function getUpdatorId()
1582
    {
1583
        self::$logger->debug('>>getUpdatorId()');
1584
        self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
1585
1586
        return $this->updated_by;
1587
    }
1588
1589
    /**
1590
     * Method for getting the date/time of when the Record was created.
1591
     *
1592
     * @return \Alpha\Model\Type\Timestamp
1593
     *
1594
     * @since 1.0
1595
     */
1596
    public function getCreateTS()
1597
    {
1598
        self::$logger->debug('>>getCreateTS()');
1599
        self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
1600
1601
        return $this->created_ts;
1602
    }
1603
1604
    /**
1605
     * Method for getting the date/time of when the Record was last updated.
1606
     *
1607
     * @return \Alpha\Model\Type\Timestamp
1608
     *
1609
     * @since 1.0
1610
     */
1611
    public function getUpdateTS()
1612
    {
1613
        self::$logger->debug('>>getUpdateTS()');
1614
        self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
1615
1616
        return $this->updated_ts;
1617
    }
1618
1619
    /**
1620
     * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this record.
1621
     *
1622
     * @param string $attributeName The name of the attribute to not save.
1623
     *
1624
     * @since 1.0
1625
     */
1626
    public function markTransient($attributeName)
1627
    {
1628
        self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
1629
        self::$logger->debug('<<markTransient');
1630
        array_push($this->transientAttributes, $attributeName);
1631
    }
1632
1633
    /**
1634
     * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this record,
1635
     * ensuring that it will be saved on the next attempt.
1636
     *
1637
     * @param string $attributeName The name of the attribute to save.
1638
     *
1639
     * @since 1.0
1640
     */
1641
    public function markPersistent($attributeName)
1642
    {
1643
        self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
1644
        self::$logger->debug('<<markPersistent');
1645
        $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
1646
    }
1647
1648
    /**
1649
     * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this record.
1650
     *
1651
     * @param string $attribute1Name The first attribute to mark unique in the database.
1652
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1653
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1654
     *
1655
     * @since 1.0
1656
     */
1657
    protected function markUnique($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1658
    {
1659
        self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1660
1661
        if (empty($attribute2Name)) {
1662
            array_push($this->uniqueAttributes, $attribute1Name);
1663
        } else {
1664
            // Process composite unique keys: add them seperated by a + sign
1665
            if ($attribute3Name == '') {
1666
                $attributes = $attribute1Name.'+'.$attribute2Name;
1667
            } else {
1668
                $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
1669
            }
1670
1671
            array_push($this->uniqueAttributes, $attributes);
1672
        }
1673
1674
        self::$logger->debug('<<markUnique');
1675
    }
1676
1677
    /**
1678
     * Returns the array of names of unique attributes on this record.
1679
     *
1680
     * @return array
1681
     *
1682
     * @since 1.1
1683
     */
1684
    public function getUniqueAttributes()
1685
    {
1686
        self::$logger->debug('>>getUniqueAttributes()');
1687
        self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
1688
1689
        return $this->uniqueAttributes;
1690
    }
1691
1692
    /**
1693
     * Gets an array of all of the names of the active database indexes for this class.
1694
     *
1695
     * @return array An array of database indexes on this table.
1696
     *
1697
     * @since 1.0
1698
     *
1699
     * @throws \Alpha\Exception\AlphaException
1700
     */
1701
    public function getIndexes()
1702
    {
1703
        self::$logger->debug('>>getIndexes()');
1704
1705
        $config = ConfigProvider::getInstance();
1706
1707
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1708
        $indexNames = $provider->getIndexes();
1709
1710
        self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
1711
1712
        return $indexNames;
1713
    }
1714
1715
    /**
1716
     * Creates a foreign key constraint (index) in the database on the given attribute.
1717
     *
1718
     * @param string $attributeName         The name of the attribute to apply the index on.
1719
     * @param string $relatedClass          The name of the related class in the format "NameObject".
1720
     * @param string $relatedClassAttribute The name of the field to relate to on the related class.
1721
     * @param string $indexName             The optional name for the index, will calculate if not provided.
1722
     *
1723
     * @since 1.0
1724
     *
1725
     * @throws \Alpha\Exception\FailedIndexCreateException
1726
     */
1727
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
1728
    {
1729
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
1730
1731
        $config = ConfigProvider::getInstance();
1732
1733
        if (method_exists($this, 'before_createForeignIndex_callback')) {
1734
            $this->{'before_createForeignIndex_callback'}();
1735
        }
1736
1737
        $relatedRecord = new $relatedClass();
1738
        $tableName = $relatedRecord->getTableName();
1739
1740
        // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
1741
        if ($this->getTableName() == $tableName) {
1742
            self::$logger->debug('<<createForeignIndex');
1743
1744
            return;
1745
        }
1746
1747
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1748
        $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName);
1749
1750
        if (method_exists($this, 'after_createForeignIndex_callback')) {
1751
            $this->{'after_createForeignIndex_callback'}();
1752
        }
1753
1754
        self::$logger->debug('<<createForeignIndex');
1755
    }
1756
1757
    /**
1758
     * Creates a unique index in the database on the given attribute(s).
1759
     *
1760
     * @param string $attribute1Name The first attribute to mark unique in the database.
1761
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1762
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1763
     *
1764
     * @since 1.0
1765
     *
1766
     * @throws \Alpha\Exception\FailedIndexCreateException
1767
     */
1768
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1769
    {
1770
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1771
1772
        if (method_exists($this, 'before_createUniqueIndex_callback')) {
1773
            $this->{'before_createUniqueIndex_callback'}();
1774
        }
1775
1776
        $config = ConfigProvider::getInstance();
1777
1778
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1779
        $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
1780
1781
        if (method_exists($this, 'after_createUniqueIndex_callback')) {
1782
            $this->{'before_createUniqueIndex_callback'}();
1783
        }
1784
1785
        self::$logger->debug('<<createUniqueIndex');
1786
    }
1787
1788
    /**
1789
     * Gets the data labels array.
1790
     *
1791
     * @return array An array of attribute labels.
1792
     *
1793
     * @since 1.0
1794
     */
1795
    public function getDataLabels()
1796
    {
1797
        self::$logger->debug('>>getDataLabels()');
1798
        self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
1799
1800
        return $this->dataLabels;
1801
    }
1802
1803
    /**
1804
     * Sets the data labels array.
1805
     *
1806
     * @param array $labels
1807
     *
1808
     * @throws \Alpha\Exception\IllegalArguementException
1809
     *
1810
     * @since 1.2
1811
     */
1812
    public function setDataLabels($labels)
1813
    {
1814
        self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
1815
1816
        if (is_array($labels)) {
1817
            $this->dataLabels = $labels;
1818
        } else {
1819
            throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
1820
        }
1821
1822
        self::$logger->debug('<<setDataLabels()');
1823
    }
1824
1825
    /**
1826
     * Gets the data label for the given attribute name.
1827
     *
1828
     * @param $att The attribute name to get the label for.
1829
     *
1830
     * @return string
1831
     *
1832
     * @since 1.0
1833
     *
1834
     * @throws \Alpha\Exception\IllegalArguementException
1835
     */
1836
    public function getDataLabel($att)
1837
    {
1838
        self::$logger->debug('>>getDataLabel(att=['.$att.'])');
1839
1840
        if (in_array($att, array_keys($this->dataLabels))) {
1841
            self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
1842
1843
            return $this->dataLabels[$att];
1844
        } else {
1845
            self::$logger->debug('<<getDataLabel');
1846
            throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
1847
        }
1848
    }
1849
1850
    /**
1851
     * Loops over the core and custom Record directories and builds an array of all of the Record class names in the system.
1852
     *
1853
     * @return array An array of business object class names.
1854
     *
1855
     * @since 1.0
1856
     */
1857
    public static function getRecordClassNames()
1858
    {
1859
        if (self::$logger == null) {
1860
            self::$logger = new Logger('ActiveRecord');
1861
        }
1862
        self::$logger->debug('>>getRecordClassNames()');
1863
1864
        $config = ConfigProvider::getInstance();
1865
1866
        $classNameArray = array();
1867
1868
        if (file_exists($config->get('app.root').'src/Model')) { // it is possible it has not been created yet...
1869
            // first get any custom records
1870
            $handle = opendir($config->get('app.root').'src/Model');
1871
1872
            // loop over the business object directory
1873
            while (false !== ($file = readdir($handle))) {
1874
                if (preg_match('/.php/', $file)) {
1875
                    $classname = 'Model\\'.mb_substr($file, 0, -4);
1876
1877
                    if (class_exists($classname)) {
1878
                        array_push($classNameArray, $classname);
1879
                    }
1880
                }
1881
            }
1882
        }
1883
1884
        // now loop over the core records provided with Alpha
1885
        if (file_exists($config->get('app.root').'Alpha/Model')) {
1886
            $handle = opendir($config->get('app.root').'Alpha/Model');
1887
        } elseif ($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model') {
1888
            $handle = opendir($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model');
1889
        }
1890
1891
        // loop over the business object directory
1892
        while (false !== ($file = readdir($handle))) {
0 ignored issues
show
Bug introduced by
The variable $handle does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1893
            if (preg_match('/.php/', $file)) {
1894
                $classname = 'Alpha\\Model\\'.mb_substr($file, 0, -4);
1895
1896
                if (class_exists($classname) && substr($classname, 0, 24) != 'Alpha\\Model\\ActiveRecord') {
1897
                    array_push($classNameArray, $classname);
1898
                }
1899
            }
1900
        }
1901
1902
        asort($classNameArray);
1903
        self::$logger->debug('<<getRecordClassNames ['.var_export($classNameArray, true).']');
1904
1905
        return $classNameArray;
1906
    }
1907
1908
    /**
1909
     * Get the array of default attribute names.
1910
     *
1911
     * @return array An array of attribute names.
1912
     *
1913
     * @since 1.0
1914
     */
1915
    public function getDefaultAttributes()
1916
    {
1917
        self::$logger->debug('>>getDefaultAttributes()');
1918
        self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
1919
1920
        return $this->defaultAttributes;
1921
    }
1922
1923
    /**
1924
     * Get the array of transient attribute names.
1925
     *
1926
     * @return array An array of attribute names.
1927
     *
1928
     * @since 1.0
1929
     */
1930
    public function getTransientAttributes()
1931
    {
1932
        self::$logger->debug('>>getTransientAttributes()');
1933
        self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
1934
1935
        return $this->transientAttributes;
1936
    }
1937
1938
    /**
1939
     * Get the array of persistent attribute names, i.e. those that are saved in the database.
1940
     *
1941
     * @return array An array of attribute names.
1942
     *
1943
     * @since 1.0
1944
     */
1945
    public function getPersistentAttributes()
1946
    {
1947
        self::$logger->debug('>>getPersistentAttributes()');
1948
1949
        $attributes = array();
1950
1951
        // get the class attributes
1952
        $reflection = new ReflectionClass(get_class($this));
1953
        $properties = $reflection->getProperties();
1954
1955
        foreach ($properties as $propObj) {
1956
            $propName = $propObj->name;
1957
1958
            // filter transient attributes
1959
            if (!in_array($propName, $this->transientAttributes)) {
1960
                array_push($attributes, $propName);
1961
            }
1962
        }
1963
1964
        self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
1965
1966
        return $attributes;
1967
    }
1968
1969
    /**
1970
     * Setter for the Object ID (ID).
1971
     *
1972
     * @param int $ID The Object ID.
1973
     *
1974
     * @since 1.0
1975
     */
1976
    public function setID($ID)
1977
    {
1978
        self::$logger->debug('>>setID(ID=['.$ID.'])');
1979
        self::$logger->debug('<<setID');
1980
        $this->ID = $ID;
1981
    }
1982
1983
    /**
1984
     * Inspector to see if the business object is transient (not presently stored in the database).
1985
     *
1986
     * @return bool
1987
     *
1988
     * @since 1.0
1989
     */
1990
    public function isTransient()
1991
    {
1992
        self::$logger->debug('>>isTransient()');
1993
1994
        if (empty($this->ID) || !isset($this->ID) || $this->ID == '00000000000') {
1995
            self::$logger->debug('<<isTransient [true]');
1996
1997
            return true;
1998
        } else {
1999
            self::$logger->debug('<<isTransient [false]');
2000
2001
            return false;
2002
        }
2003
    }
2004
2005
    /**
2006
     * Get the last database query run on this object.
2007
     *
2008
     * @return string An SQL query string.
2009
     *
2010
     * @since 1.0
2011
     */
2012
    public function getLastQuery()
2013
    {
2014
        self::$logger->debug('>>getLastQuery()');
2015
        self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
2016
2017
        return $this->lastQuery;
2018
    }
2019
2020
    /**
2021
     * Unsets all of the attributes of this object to null.
2022
     *
2023
     * @since 1.0
2024
     */
2025
    private function clear()
2026
    {
2027
        self::$logger->debug('>>clear()');
2028
2029
        // get the class attributes
2030
        $reflection = new ReflectionClass(get_class($this));
2031
        $properties = $reflection->getProperties();
2032
2033
        foreach ($properties as $propObj) {
2034
            $propName = $propObj->name;
2035
            if (!$propObj->isPrivate()) {
2036
                unset($this->$propName);
2037
            }
2038
        }
2039
2040
        self::$logger->debug('<<clear');
2041
    }
2042
2043
    /**
2044
     * Reloads the object from the database, overwritting any attribute values in memory.
2045
     *
2046
     * @since 1.0
2047
     *
2048
     * @throws \Alpha\Exception\AlphaException
2049
     */
2050
    public function reload()
2051
    {
2052
        self::$logger->debug('>>reload()');
2053
2054
        if (!$this->isTransient()) {
2055
            $this->load($this->getID());
2056
        } else {
2057
            throw new AlphaException('Cannot reload transient object from database!');
2058
        }
2059
        self::$logger->debug('<<reload');
2060
    }
2061
2062
    /**
2063
     * Loads the definition from the file system for the Record class name provided.
2064
     *
2065
     * @param string $classname The name of the business object class name.
2066
     *
2067
     * @since 1.0
2068
     *
2069
     * @throws \Alpha\Exception\IllegalArguementException
2070
     *
2071
     * @deprecated Use autoloader!
2072
     */
2073
    public static function loadClassDef($classname)
2074
    {
2075
        if (self::$logger == null) {
2076
            self::$logger = new Logger('ActiveRecord');
2077
        }
2078
        self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
2079
2080
        $config = ConfigProvider::getInstance();
2081
2082
        if (file_exists($config->get('app.root').'Model/'.$classname.'.php')) {
2083
            require_once $config->get('app.root').'Model/'.$classname.'.php';
2084
        } elseif (file_exists($config->get('app.root').'alpha/Alpha/Model/'.$classname.'.php')) {
2085
            require_once $config->get('app.root').'alpha/Alpha/Model/'.$classname.'.php';
2086
        } elseif (file_exists($config->get('app.root').'alpha/Alpha/Model/Types/'.$classname.'.php')) {
2087
            require_once $config->get('app.root').'alpha/Alpha/Model/Types/'.$classname.'.php';
2088
        } else {
2089
            throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
2090
        }
2091
2092
        self::$logger->debug('<<loadClassDef');
2093
    }
2094
2095
    /**
2096
     * Checks that a record exists for the Record in the database.
2097
     *
2098
     * @param int $ID The Object ID of the object we want to see whether it exists or not.
2099
     *
2100
     * @return bool
2101
     *
2102
     * @since 1.0
2103
     *
2104
     * @throws \Alpha\Exception\AlphaException
2105
     */
2106
    public function checkRecordExists($ID)
2107
    {
2108
        self::$logger->debug('>>checkRecordExists(ID=['.$ID.'])');
2109
2110
        if (method_exists($this, 'before_checkRecordExists_callback')) {
2111
            $this->{'before_checkRecordExists_callback'}();
2112
        }
2113
2114
        $config = ConfigProvider::getInstance();
2115
2116
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
2117
        $recordExists = $provider->checkRecordExists($ID);
2118
2119
        if (method_exists($this, 'after_checkRecordExists_callback')) {
2120
            $this->{'after_checkRecordExists_callback'}();
2121
        }
2122
2123
        self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
2124
2125
        return $recordExists;
2126
    }
2127
2128
    /**
2129
     * Checks to see if the table name matches the classname, and if not if the table
2130
     * name matches the classname name of another record, i.e. the table is used to store
2131
     * multiple types of records.
2132
     *
2133
     * @return bool
2134
     *
2135
     * @since 1.0
2136
     *
2137
     * @throws \Alpha\Exception\BadTableNameException
2138
     */
2139
    public function isTableOverloaded()
2140
    {
2141
        self::$logger->debug('>>isTableOverloaded()');
2142
2143
        $config = ConfigProvider::getInstance();
2144
2145
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
2146
        $isOverloaded = $provider->isTableOverloaded();
2147
2148
        self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
2149
2150
        return $isOverloaded;
2151
    }
2152
2153
    /**
2154
     * Starts a new database transaction.
2155
     *
2156
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2157
     *
2158
     * @since 1.0
2159
     *
2160
     * @throws \Alpha\Exception\AlphaException
2161
     */
2162
    public static function begin($Record = null)
2163
    {
2164
        if (self::$logger == null) {
2165
            self::$logger = new Logger('ActiveRecord');
2166
        }
2167
        self::$logger->debug('>>begin()');
2168
2169
        $config = ConfigProvider::getInstance();
2170
2171
        if (isset($Record)) {
2172
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2173
        } else {
2174
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2175
        }
2176
2177
        try {
2178
            $provider->begin();
2179
        } catch (\Exception $e) {
2180
            throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
2181
        }
2182
2183
        self::$logger->debug('<<begin');
2184
    }
2185
2186
    /**
2187
     * Commits the current database transaction.
2188
     *
2189
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2190
     *
2191
     * @since 1.0
2192
     *
2193
     * @throws \Alpha\Exception\FailedSaveException
2194
     */
2195
    public static function commit($Record = null)
2196
    {
2197
        if (self::$logger == null) {
2198
            self::$logger = new Logger('ActiveRecord');
2199
        }
2200
        self::$logger->debug('>>commit()');
2201
2202
        $config = ConfigProvider::getInstance();
2203
2204
        if (isset($Record)) {
2205
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2206
        } else {
2207
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2208
        }
2209
2210
        try {
2211
            $provider->commit();
2212
        } catch (\Exception $e) {
2213
            throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
2214
        }
2215
2216
        self::$logger->debug('<<commit');
2217
    }
2218
2219
    /**
2220
     * Aborts the current database transaction.
2221
     *
2222
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2223
     *
2224
     * @since 1.0
2225
     *
2226
     * @throws \Alpha\Exception\AlphaException
2227
     */
2228
    public static function rollback($Record = null)
2229
    {
2230
        if (self::$logger == null) {
2231
            self::$logger = new Logger('ActiveRecord');
2232
        }
2233
        self::$logger->debug('>>rollback()');
2234
2235
        $config = ConfigProvider::getInstance();
2236
2237
        if (isset($Record)) {
2238
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2239
        } else {
2240
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2241
        }
2242
2243
        try {
2244
            $provider->rollback();
2245
        } catch (\Exception $e) {
2246
            throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
2247
        }
2248
2249
        self::$logger->debug('<<rollback');
2250
    }
2251
2252
    /**
2253
     * Static method that tries to determine if the system database has been installed or not.
2254
     *
2255
     * @return bool
2256
     *
2257
     * @since 1.0
2258
     */
2259
    public static function isInstalled()
2260
    {
2261
        if (self::$logger == null) {
2262
            self::$logger = new Logger('ActiveRecord');
2263
        }
2264
        self::$logger->debug('>>isInstalled()');
2265
2266
        $config = ConfigProvider::getInstance();
0 ignored issues
show
Unused Code introduced by
$config is not used, you could remove the assignment.

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

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

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

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

Loading history...
2267
2268
        /*
2269
         * Install conditions are:
2270
         *
2271
         * 1. person table exists
2272
         * 2. rights table exists
2273
         */
2274
        if (self::checkRecordTableExists('Alpha\Model\Person') && self::checkRecordTableExists('Alpha\Model\Rights')) {
2275
            self::$logger->debug('<<isInstalled [true]');
2276
2277
            return true;
2278
        } else {
2279
            self::$logger->debug('<<isInstalled [false]');
2280
2281
            return false;
2282
        }
2283
    }
2284
2285
    /**
2286
     * Returns true if the Record has a Relation property called tags, false otherwise.
2287
     *
2288
     * @return bool
2289
     *
2290
     * @since 1.0
2291
     */
2292
    public function isTagged()
2293
    {
2294
        if (isset($this->taggedAttributes) && isset($this->tags) && $this->tags instanceof \Alpha\Model\Type\Relation) {
0 ignored issues
show
Bug introduced by
The property tags does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2295
            return true;
2296
        } else {
2297
            return false;
2298
        }
2299
    }
2300
2301
    /**
2302
     * Returns the contents of the taggedAttributes array, or an empty array if that does not exist.
2303
     *
2304
     * @return array
2305
     *
2306
     * @since 1.2.3
2307
     */
2308
    public function getTaggedAttributes()
2309
    {
2310
        if ($this->isTagged()) {
2311
            return $this->taggedAttributes;
0 ignored issues
show
Bug introduced by
The property taggedAttributes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
2312
        } else {
2313
            return array();
2314
        }
2315
    }
2316
2317
    /**
2318
     * Setter for the Record version number.
2319
     *
2320
     * @param int $versionNumber The version number.
2321
     *
2322
     * @since 1.0
2323
     */
2324
    private function setVersion($versionNumber)
2325
    {
2326
        $this->version_num->setValue($versionNumber);
2327
    }
2328
2329
    /**
2330
     * Cast a Record to another type of record.  A new Record will be returned with the same ID and
2331
     * version_num as the old record, so this is NOT a true cast but is a copy.  All attribute
2332
     * values will be copied accross.
2333
     *
2334
     * @param string                    $targetClassName     The fully-qualified name of the target Record class.
2335
     * @param \Alpha\Model\ActiveRecord $originalRecord      The original business object.
2336
     *
2337
     * @return \Alpha\Model\ActiveRecord The new business object resulting from the cast.
2338
     *
2339
     * @since 1.0
2340
     */
2341
    public function cast($targetClassName, $originalRecord)
2342
    {
2343
        $Record = new $targetClassName();
2344
        $Record->setID($originalRecord->getID());
2345
        $Record->setVersion($originalRecord->getVersion());
2346
2347
        // get the class attributes
2348
        $originalRecordreflection = new ReflectionClass(get_class($originalRecord));
2349
        $originalRecordproperties = $originalRecordreflection->getProperties();
2350
        $newRecordreflection = new ReflectionClass($targetClassName);
2351
        $newRecordproperties = $newRecordreflection->getProperties();
2352
2353
        // copy the property values from the old Record to the new record
2354
2355
        if (count($originalRecordproperties) < count($newRecordproperties)) {
2356
            // the original Record is smaller, so loop over its properties
2357
            foreach ($originalRecordproperties as $propObj) {
2358
                $propName = $propObj->name;
2359
                if (!in_array($propName, $this->transientAttributes)) {
2360
                    $Record->set($propName, $originalRecord->get($propName));
2361
                }
2362
            }
2363
        } else {
2364
            // the new Record is smaller, so loop over its properties
2365
            foreach ($newRecordproperties as $propObj) {
2366
                $propName = $propObj->name;
2367
                if (!in_array($propName, $this->transientAttributes)) {
2368
                    $Record->set($propName, $originalRecord->get($propName));
2369
                }
2370
            }
2371
        }
2372
2373
        return $Record;
2374
    }
2375
2376
    /**
2377
     * Returns the simple class name, stripped of the namespace.
2378
     *
2379
     * @return string
2380
     *
2381
     * @since 1.0
2382
     */
2383
    public function getFriendlyClassName()
2384
    {
2385
        $reflectClass = new ReflectionClass($this);
2386
2387
        return $reflectClass->getShortname();
2388
    }
2389
2390
    /**
2391
     * Check to see if an attribute exists on the record.
2392
     *
2393
     * @param $attribute The attribute name.
2394
     *
2395
     * @return bool
2396
     *
2397
     * @since 1.0
2398
     */
2399
    public function hasAttribute($attribute)
2400
    {
2401
        try {
2402
            $exists = $this->$attribute;
0 ignored issues
show
Unused Code introduced by
$exists is not used, you could remove the assignment.

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

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

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

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

Loading history...
2403
2404
            return true;
2405
        } catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) { return false; } does not seem to be reachable.

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

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

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

    return false;
}

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

Loading history...
2406
            return false;
2407
        }
2408
    }
2409
2410
    /**
2411
     * Stores the business object to the configured cache instance.
2412
     *
2413
     * @since 1.1
2414
     */
2415
    public function addToCache()
2416
    {
2417
        self::$logger->debug('>>addToCache()');
2418
        $config = ConfigProvider::getInstance();
2419
2420
        try {
2421
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2422
            $cache->set(get_class($this).'-'.$this->getID(), $this, 3600);
2423
        } catch (\Exception $e) {
2424
            self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').'] 
2425
                instance: ['.$e->getMessage().']');
2426
        }
2427
2428
        self::$logger->debug('<<addToCache');
2429
    }
2430
2431
    /**
2432
     * Removes the business object from the configured cache instance.
2433
     *
2434
     * @since 1.1
2435
     */
2436
    public function removeFromCache()
2437
    {
2438
        self::$logger->debug('>>removeFromCache()');
2439
        $config = ConfigProvider::getInstance();
2440
2441
        try {
2442
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2443
            $cache->delete(get_class($this).'-'.$this->getID());
2444
        } catch (\Exception $e) {
2445
            self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
2446
                instance: ['.$e->getMessage().']');
2447
        }
2448
2449
        self::$logger->debug('<<removeFromCache');
2450
    }
2451
2452
    /**
2453
     * Attempts to load the business object from the configured cache instance.
2454
     *
2455
     * @since 1.1
2456
     *
2457
     * @return bool
2458
     */
2459
    public function loadFromCache()
2460
    {
2461
        self::$logger->debug('>>loadFromCache()');
2462
        $config = ConfigProvider::getInstance();
2463
2464
        try {
2465
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2466
            $Record = $cache->get(get_class($this).'-'.$this->getID());
2467
2468
            if (!$Record) {
2469
                self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getID().']');
2470
                self::$logger->debug('<<loadFromCache: [false]');
2471
2472
                return false;
2473
            } else {
2474
                // get the class attributes
2475
                $reflection = new ReflectionClass(get_class($this));
2476
                $properties = $reflection->getProperties();
2477
2478
                foreach ($properties as $propObj) {
2479
                    $propName = $propObj->name;
2480
2481
                    // filter transient attributes
2482
                    if (!in_array($propName, $this->transientAttributes)) {
2483
                        $this->set($propName, $Record->get($propName, true));
2484
                    } elseif (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
2485
                        $prop = $this->getPropObject($propName);
2486
2487
                        // handle the setting of ONE-TO-MANY relation values
2488
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
2489
                            $this->set($propObj->name, $this->getID());
2490
                        }
2491
                    }
2492
                }
2493
2494
                self::$logger->debug('<<loadFromCache: [true]');
2495
2496
                return true;
2497
            }
2498
        } catch (\Exception $e) {
2499
            self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
2500
             instance: ['.$e->getMessage().']');
2501
2502
            self::$logger->debug('<<loadFromCache: [false]');
2503
2504
            return false;
2505
        }
2506
    }
2507
2508
    /**
2509
     * Sets the last query executed on this business object.
2510
     *
2511
     * @param string $query
2512
     *
2513
     * @since 1.1
2514
     */
2515
    public function setLastQuery($query)
2516
    {
2517
        self::$logger->sql($query);
2518
        $this->lastQuery = $query;
2519
    }
2520
2521
    /**
2522
     * Re-initialize the static logger property on the Record after de-serialize, as PHP does
2523
     * not serialize static properties.
2524
     *
2525
     * @since 1.2
2526
     */
2527
    public function __wakeup()
2528
    {
2529
        if (self::$logger == null) {
2530
            self::$logger = new Logger(get_class($this));
2531
        }
2532
    }
2533
2534
    /**
2535
     * Sets maintainHistory attribute on this DAO.
2536
     *
2537
     * @param bool $maintainHistory
2538
     *
2539
     * @throws \Alpha\Exception\IllegalArguementException
2540
     *
2541
     * @since 1.2
2542
     */
2543
    public function setMaintainHistory($maintainHistory)
2544
    {
2545
        if (!is_bool($maintainHistory)) {
2546
            throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
2547
        }
2548
2549
        $this->maintainHistory = $maintainHistory;
2550
    }
2551
2552
    /**
2553
     * Gets the value of the  maintainHistory attribute.
2554
     *
2555
     * @return bool
2556
     *
2557
     * @since 1.2
2558
     */
2559
    public function getMaintainHistory()
2560
    {
2561
        return $this->maintainHistory;
2562
    }
2563
2564
    /**
2565
     * Return a hash array of the object containing attribute names and simplfied values.
2566
     *
2567
     * @return array
2568
     *
2569
     * @since  1.2.4
2570
     */
2571
    public function toArray()
2572
    {
2573
        // get the class attributes
2574
        $reflection = new ReflectionClass(get_class($this));
2575
        $properties = $reflection->getProperties();
2576
2577
        $propArray = array();
2578
2579
        foreach ($properties as $propObj) {
2580
            $propName = $propObj->name;
2581
2582
            if (!in_array($propName, $this->transientAttributes)) {
2583
                $val = $this->get($propName);
2584
2585
                if (is_object($val)) {
2586
                    $val = $val->getValue();
2587
                }
2588
2589
                $propArray[$propName] = $val;
2590
            }
2591
        }
2592
2593
        return $propArray;
2594
    }
2595
2596
    /**
2597
     * Check to see if the configured database exists.
2598
     *
2599
     * @return bool
2600
     *
2601
     * @since 2.0
2602
     */
2603
    public static function checkDatabaseExists()
2604
    {
2605
        $config = ConfigProvider::getInstance();
2606
2607
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2608
2609
        return $provider->checkDatabaseExists();
2610
    }
2611
2612
    /**
2613
     * Creates the configured database.
2614
     *
2615
     * @throws \Alpha\Exception\AlphaException
2616
     *
2617
     * @since 2.0
2618
     */
2619
    public static function createDatabase()
2620
    {
2621
        $config = ConfigProvider::getInstance();
2622
2623
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2624
        $provider->createDatabase();
2625
    }
2626
2627
    /**
2628
     * Drops the configured database.
2629
     *
2630
     * @throws \Alpha\Exception\AlphaException
2631
     *
2632
     * @since 2.0
2633
     */
2634
    public static function dropDatabase()
2635
    {
2636
        $config = ConfigProvider::getInstance();
2637
2638
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2639
        $provider->dropDatabase();
2640
    }
2641
}
2642