Completed
Push — develop ( 4f2388...3f4b0b )
by John
02:50
created

ActiveRecord::getBOClassNames()   C

Complexity

Conditions 12
Paths 48

Size

Total Lines 50
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 50
rs 5.3904
cc 12
eloc 25
nc 48
nop 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\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 $OID;
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 OID 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 OID 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('OID', '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')->getOID() : 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 $OID.
253
     *
254
     * @param int $OID     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($OID, $version = 0)
262
    {
263
        self::$logger->debug('>>load(OID=['.$OID.'], 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->OID = $OID;
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($OID, $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 $OID 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($OID)
305
    {
306
        self::$logger->debug('>>loadAllOldVersions(OID=['.$OID.'])');
307
308
        $config = ConfigProvider::getInstance();
309
310
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
311
        $objects = $provider->loadAllOldVersions($OID);
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 = 'OID', $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 = 'OID', $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 = 'OID', $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 = 'OID', $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->OID 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->getOID());
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->getOID());
744
                    } else {
745
                        $lookUp->deleteAllByAttribute('rightID', $this->getOID());
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 OID as its transient
763
                    $prop->setValue($this->getOID());
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
                        $OIDs = explode(',', $hashArray[$propName]);
1044
                        $rel->setRelatedOIDs($OIDs);
1045
                        $this->$propName = $rel;
1046
                    }
1047
                }
1048
            }
1049
        }
1050
1051
        self::$logger->debug('<<populateFromArray');
1052
    }
1053
1054
    /**
1055
     * Gets the maximum OID value from the database for this class type.
1056
     *
1057
     * @return int The maximum OID 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 OID for the object in zero-padded format (same as getOID()).  This version is designed
1158
     * to be overridden in case you want to do something different with the ID of your objects outside
1159
     * of the standard 11 digit OID sequence used internally in Alpha.
1160
     *
1161
     * @return int 11 digit zero-padded OID value.
1162
     *
1163
     * @since 1.0
1164
     */
1165
    public function getID()
1166
    {
1167
        self::$logger->debug('>>getID()');
1168
        $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
1169
        self::$logger->debug('<<getID ['.$oid.']');
1170
1171
        return $oid;
1172
    }
1173
1174
    /**
1175
     * Gets the OID for the object in zero-padded format (same as getID()).  This version is final so cannot
1176
     * be overridden.
1177
     *
1178
     * @return int 11 digit zero-padded OID value.
1179
     *
1180
     * @since 1.0
1181
     */
1182
    final public function getOID()
1183
    {
1184
        if (self::$logger == null) {
1185
            self::$logger = new Logger('ActiveRecord');
1186
        }
1187
        self::$logger->debug('>>getOID()');
1188
        $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
1189
        self::$logger->debug('<<getOID ['.$oid.']');
1190
1191
        return $oid;
1192
    }
1193
1194
    /**
1195
     * Method for getting version number of the object.
1196
     *
1197
     * @return \Alpha\Model\Type\Integer The object version number.
1198
     *
1199
     * @since 1.0
1200
     */
1201
    public function getVersionNumber()
1202
    {
1203
        self::$logger->debug('>>getVersionNumber()');
1204
        self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
1205
1206
        return $this->version_num;
1207
    }
1208
1209
    /**
1210
     * Populate all of the enum options for this object from the database.
1211
     *
1212
     * @since 1.0
1213
     *
1214
     * @throws \Alpha\Exception\AlphaException
1215
     */
1216
    protected function setEnumOptions()
1217
    {
1218
        self::$logger->debug('>>setEnumOptions()');
1219
1220
        if (method_exists($this, 'before_setEnumOptions_callback')) {
1221
            $this->{'before_setEnumOptions_callback'}();
1222
        }
1223
1224
        $config = ConfigProvider::getInstance();
1225
1226
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1227
        try {
1228
            $provider->setEnumOptions();
1229
        } catch (NotImplementedException $e) {
1230
            self::$logger->debug($e->getMessage());
1231
        }
1232
1233
        self::$logger->debug('<<setEnumOptions');
1234
    }
1235
1236
    /**
1237
     * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
1238
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
1239
     * get.ucfirst($prop) method even if it exists, false otherwise (default).
1240
     *
1241
     * @param string $prop           The name of the object property to get.
1242
     * @param bool   $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
1243
     *
1244
     * @return mixed The property value.
1245
     *
1246
     * @since 1.0
1247
     *
1248
     * @throws \Alpha\Exception\IllegalArguementException
1249
     * @throws \Alpha\Exception\AlphaException
1250
     */
1251
    public function get($prop, $noChildMethods = false)
1252
    {
1253
        if (self::$logger == null) {
1254
            self::$logger = new Logger('ActiveRecord');
1255
        }
1256
1257
        self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
1258
1259
        if (method_exists($this, 'before_get_callback')) {
1260
            $this->{'before_get_callback'}();
1261
        }
1262
1263
        if (empty($prop)) {
1264
            throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
1265
        }
1266
1267
        // handle attributes with a get.ucfirst($prop) method
1268
        if (!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
1269
            if (method_exists($this, 'after_get_callback')) {
1270
                $this->{'after_get_callback'}();
1271
            }
1272
1273
            $methodName = 'get'.ucfirst($prop);
1274
1275
            self::$logger->debug('<<get ['.print_r($this->$methodName(), true).'])');
1276
1277
            return $this->$methodName();
1278
        } else {
1279
            // handle attributes with no dedicated child get.ucfirst($prop) method
1280
            if (isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
1281
                if (method_exists($this, 'after_get_callback')) {
1282
                    $this->{'after_get_callback'}();
1283
                }
1284
1285
                // complex types will have a getValue() method, return the value of that
1286
                self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
1287
1288
                return $this->$prop->getValue();
1289
            } elseif (isset($this->$prop)) {
1290
                if (method_exists($this, 'after_get_callback')) {
1291
                    $this->{'after_get_callback'}();
1292
                }
1293
1294
                // simple types returned as-is
1295
                self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
1296
1297
                return $this->$prop;
1298
            } else {
1299
                self::$logger->debug('<<get');
1300
                throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
1301
            }
1302
        }
1303
    }
1304
1305
    /**
1306
     * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
1307
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
1308
     * any get.ucfirst($prop) method even if it exists, false otherwise (default).
1309
     *
1310
     * @param string $prop           The name of the property to set.
1311
     * @param mixed  $value          The value of the property to set.
1312
     * @param bool   $noChildMethods Set to true if you do not want to use setters in the child object, defaults to false.
1313
     *
1314
     * @since 1.0
1315
     *
1316
     * @throws \Alpha\Exception\AlphaException
1317
     */
1318
    public function set($prop, $value, $noChildMethods = false)
1319
    {
1320
        self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
1321
1322
        if (method_exists($this, 'before_set_callback')) {
1323
            $this->{'before_set_callback'}();
1324
        }
1325
1326
        // handle attributes with a set.ucfirst($prop) method
1327
        if (!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
1328
            if (method_exists($this, 'after_set_callback')) {
1329
                $this->{'after_set_callback'}();
1330
            }
1331
1332
            $methodName = 'set'.ucfirst($prop);
1333
1334
            $this->$methodName($value);
1335
        } else {
1336
            // handle attributes with no dedicated child set.ucfirst($prop) method
1337
            if (isset($this->$prop)) {
1338
                if (method_exists($this, 'after_set_callback')) {
1339
                    $this->{'after_set_callback'}();
1340
                }
1341
1342
                // complex types will have a setValue() method to call
1343
                if (is_object($this->$prop) && get_class($this->$prop) !== false) {
1344
                    if (mb_strtoupper(get_class($this->$prop)) != 'DATE' && mb_strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
1345
                        $this->$prop->setValue($value);
1346
                    } else {
1347
                        // Date and Timestamp objects have a special setter accepting a string
1348
                        $this->$prop->populateFromString($value);
1349
                    }
1350
                } else {
1351
                    // simple types set directly
1352
                    $this->$prop = $value;
1353
                }
1354
            } else {
1355
                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.');
1356
            }
1357
        }
1358
        self::$logger->debug('<<set');
1359
    }
1360
1361
    /**
1362
     * Gets the property object rather than the value for complex attributes.  Returns false if
1363
     * the property exists but is private.
1364
     *
1365
     * @param string $prop The name of the property we are getting.
1366
     *
1367
     * @return \Alpha\Model\Type\Type|bool The complex type object found.
1368
     *
1369
     * @since 1.0
1370
     *
1371
     * @throws \Alpha\Exception\IllegalArguementException
1372
     */
1373
    public function getPropObject($prop)
1374
    {
1375
        self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
1376
1377
        if (method_exists($this, 'before_getPropObject_callback')) {
1378
            $this->{'before_getPropObject_callback'}();
1379
        }
1380
1381
        // get the class attributes
1382
        $reflection = new \ReflectionObject($this);
1383
        $properties = $reflection->getProperties();
1384
1385
        // firstly, check for private
1386
        $attribute = new ReflectionProperty($this, $prop);
1387
1388
        if ($attribute->isPrivate()) {
1389
            if (method_exists($this, 'after_getPropObject_callback')) {
1390
                $this->{'after_getPropObject_callback'}();
1391
            }
1392
1393
            self::$logger->debug('<<getPropObject [false]');
1394
1395
            return false;
1396
        }
1397
1398
        foreach ($properties as $propObj) {
1399
            $propName = $propObj->name;
1400
1401
            if ($prop == $propName) {
1402
                if (method_exists($this, 'after_getPropObject_callback')) {
1403
                    $this->{'after_getPropObject_callback'}();
1404
                }
1405
1406
                self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
1407
1408
                return $this->$prop;
1409
            }
1410
        }
1411
1412
        self::$logger->debug('<<getPropObject');
1413
        throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
1414
    }
1415
1416
    /**
1417
     * Checks to see if the table exists in the database for the current business class.
1418
     *
1419
     * @param bool $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1420
     *
1421
     * @return bool
1422
     *
1423
     * @since 1.0
1424
     *
1425
     * @throws \Alpha\Exception\AlphaException
1426
     */
1427
    public function checkTableExists($checkHistoryTable = false)
1428
    {
1429
        self::$logger->debug('>>checkTableExists()');
1430
1431
        if (method_exists($this, 'before_checkTableExists_callback')) {
1432
            $this->{'before_checkTableExists_callback'}();
1433
        }
1434
1435
        $config = ConfigProvider::getInstance();
1436
1437
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1438
        $tableExists = $provider->checkTableExists($checkHistoryTable);
1439
1440
        if (method_exists($this, 'after_checkTableExists_callback')) {
1441
            $this->{'after_checkTableExists_callback'}();
1442
        }
1443
1444
        self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1445
1446
        return $tableExists;
1447
    }
1448
1449
    /**
1450
     * Static method to check the database and see if the table for the indicated Record class name
1451
     * exists (assumes table name will be $RecordClassName less "Object").
1452
     *
1453
     * @param string $RecordClassName       The name of the business object class we are checking.
1454
     * @param bool   $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1455
     *
1456
     * @return bool
1457
     *
1458
     * @since 1.0
1459
     *
1460
     * @throws \Alpha\Exception\AlphaException
1461
     */
1462
    public static function checkRecordTableExists($RecordClassName, $checkHistoryTable = false)
1463
    {
1464
        if (self::$logger == null) {
1465
            self::$logger = new Logger('ActiveRecord');
1466
        }
1467
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$RecordClassName.'])');
1468
1469
        $config = ConfigProvider::getInstance();
1470
1471
        $provider = $config->get('db.provider.name');
1472
1473
        $tableExists = $provider::checkRecordTableExists($RecordClassName, $checkHistoryTable);
1474
1475
        self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1476
1477
        return $tableExists;
1478
    }
1479
1480
    /**
1481
     * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
1482
     * database table is in sync with the class definition.
1483
     *
1484
     * @return bool
1485
     *
1486
     * @since 1.0
1487
     *
1488
     * @throws \Alpha\Exception\AlphaException
1489
     */
1490
    public function checkTableNeedsUpdate()
1491
    {
1492
        self::$logger->debug('>>checkTableNeedsUpdate()');
1493
1494
        $config = ConfigProvider::getInstance();
1495
1496
        if (method_exists($this, 'before_checkTableNeedsUpdate_callback')) {
1497
            $this->{'before_checkTableNeedsUpdate_callback'}();
1498
        }
1499
1500
        $tableExists = $this->checkTableExists();
1501
1502
        if (!$tableExists) {
1503
            self::$logger->debug('<<checkTableNeedsUpdate [true]');
1504
1505
            return true;
1506
        } else {
1507
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1508
            $updateRequired = $provider->checkTableNeedsUpdate();
1509
1510
            if (method_exists($this, 'after_checkTableNeedsUpdate_callback')) {
1511
                $this->{'after_checkTableNeedsUpdate_callback'}();
1512
            }
1513
1514
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1515
1516
            return $updateRequired;
1517
        }
1518
    }
1519
1520
    /**
1521
     * Returns an array containing any properties on the class which have not been created on the database
1522
     * table yet.
1523
     *
1524
     * @return array An array of missing fields in the database table.
1525
     *
1526
     * @since 1.0
1527
     *
1528
     * @throws \Alpha\Exception\AlphaException
1529
     */
1530
    public function findMissingFields()
1531
    {
1532
        self::$logger->debug('>>findMissingFields()');
1533
1534
        $config = ConfigProvider::getInstance();
1535
1536
        if (method_exists($this, 'before_findMissingFields_callback')) {
1537
            $this->{'before_findMissingFields_callback'}();
1538
        }
1539
1540
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1541
        $missingFields = $provider->findMissingFields();
1542
1543
        if (method_exists($this, 'after_findMissingFields_callback')) {
1544
            $this->{'after_findMissingFields_callback'}();
1545
        }
1546
1547
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1548
1549
        return $missingFields;
1550
    }
1551
1552
    /**
1553
     * Getter for the TABLE_NAME, which should be set by a child of this class.
1554
     *
1555
     * @return string The table name in the database.
1556
     *
1557
     * @since 1.0
1558
     *
1559
     * @throws \Alpha\Exception\AlphaException
1560
     */
1561
    public function getTableName()
1562
    {
1563
        self::$logger->debug('>>getTableName()');
1564
1565
        $className = get_class($this);
1566
1567
        $tableName = $className::TABLE_NAME;
1568
1569
        if (!empty($tableName)) {
1570
            self::$logger->debug('<<getTableName ['.$tableName.']');
1571
1572
            return $tableName;
1573
        } else {
1574
            throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
1575
        }
1576
    }
1577
1578
    /**
1579
     * Method for getting the OID of the person who created this record.
1580
     *
1581
     * @return \Alpha\Model\Type\Integer The OID of the creator.
1582
     *
1583
     * @since 1.0
1584
     */
1585
    public function getCreatorId()
1586
    {
1587
        self::$logger->debug('>>getCreatorId()');
1588
        self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
1589
1590
        return $this->created_by;
1591
    }
1592
1593
    /**
1594
     * Method for getting the OID of the person who updated this record.
1595
     *
1596
     * @return \Alpha\Model\Type\Integer The OID of the updator.
1597
     *
1598
     * @since 1.0
1599
     */
1600
    public function getUpdatorId()
1601
    {
1602
        self::$logger->debug('>>getUpdatorId()');
1603
        self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
1604
1605
        return $this->updated_by;
1606
    }
1607
1608
    /**
1609
     * Method for getting the date/time of when the Record was created.
1610
     *
1611
     * @return \Alpha\Model\Type\Timestamp
1612
     *
1613
     * @since 1.0
1614
     */
1615
    public function getCreateTS()
1616
    {
1617
        self::$logger->debug('>>getCreateTS()');
1618
        self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
1619
1620
        return $this->created_ts;
1621
    }
1622
1623
    /**
1624
     * Method for getting the date/time of when the Record was last updated.
1625
     *
1626
     * @return \Alpha\Model\Type\Timestamp
1627
     *
1628
     * @since 1.0
1629
     */
1630
    public function getUpdateTS()
1631
    {
1632
        self::$logger->debug('>>getUpdateTS()');
1633
        self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
1634
1635
        return $this->updated_ts;
1636
    }
1637
1638
    /**
1639
     * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this record.
1640
     *
1641
     * @param string $attributeName The name of the attribute to not save.
1642
     *
1643
     * @since 1.0
1644
     */
1645
    public function markTransient($attributeName)
1646
    {
1647
        self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
1648
        self::$logger->debug('<<markTransient');
1649
        array_push($this->transientAttributes, $attributeName);
1650
    }
1651
1652
    /**
1653
     * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this record,
1654
     * ensuring that it will be saved on the next attempt.
1655
     *
1656
     * @param string $attributeName The name of the attribute to save.
1657
     *
1658
     * @since 1.0
1659
     */
1660
    public function markPersistent($attributeName)
1661
    {
1662
        self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
1663
        self::$logger->debug('<<markPersistent');
1664
        $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
1665
    }
1666
1667
    /**
1668
     * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this record.
1669
     *
1670
     * @param string $attribute1Name The first attribute to mark unique in the database.
1671
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1672
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1673
     *
1674
     * @since 1.0
1675
     */
1676
    protected function markUnique($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1677
    {
1678
        self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1679
1680
        if (empty($attribute2Name)) {
1681
            array_push($this->uniqueAttributes, $attribute1Name);
1682
        } else {
1683
            // Process composite unique keys: add them seperated by a + sign
1684
            if ($attribute3Name == '') {
1685
                $attributes = $attribute1Name.'+'.$attribute2Name;
1686
            } else {
1687
                $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
1688
            }
1689
1690
            array_push($this->uniqueAttributes, $attributes);
1691
        }
1692
1693
        self::$logger->debug('<<markUnique');
1694
    }
1695
1696
    /**
1697
     * Returns the array of names of unique attributes on this record.
1698
     *
1699
     * @return array
1700
     *
1701
     * @since 1.1
1702
     */
1703
    public function getUniqueAttributes()
1704
    {
1705
        self::$logger->debug('>>getUniqueAttributes()');
1706
        self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
1707
1708
        return $this->uniqueAttributes;
1709
    }
1710
1711
    /**
1712
     * Gets an array of all of the names of the active database indexes for this class.
1713
     *
1714
     * @return array An array of database indexes on this table.
1715
     *
1716
     * @since 1.0
1717
     *
1718
     * @throws \Alpha\Exception\AlphaException
1719
     */
1720
    public function getIndexes()
1721
    {
1722
        self::$logger->debug('>>getIndexes()');
1723
1724
        $config = ConfigProvider::getInstance();
1725
1726
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1727
        $indexNames = $provider->getIndexes();
1728
1729
        self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
1730
1731
        return $indexNames;
1732
    }
1733
1734
    /**
1735
     * Creates a foreign key constraint (index) in the database on the given attribute.
1736
     *
1737
     * @param string $attributeName         The name of the attribute to apply the index on.
1738
     * @param string $relatedClass          The name of the related class in the format "NameObject".
1739
     * @param string $relatedClassAttribute The name of the field to relate to on the related class.
1740
     * @param string $indexName             The optional name for the index, will calculate if not provided.
1741
     *
1742
     * @since 1.0
1743
     *
1744
     * @throws \Alpha\Exception\FailedIndexCreateException
1745
     */
1746
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
1747
    {
1748
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
1749
1750
        $config = ConfigProvider::getInstance();
1751
1752
        if (method_exists($this, 'before_createForeignIndex_callback')) {
1753
            $this->{'before_createForeignIndex_callback'}();
1754
        }
1755
1756
        $relatedRecord = new $relatedClass();
1757
        $tableName = $relatedRecord->getTableName();
1758
1759
        // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
1760
        if ($this->getTableName() == $tableName) {
1761
            self::$logger->debug('<<createForeignIndex');
1762
1763
            return;
1764
        }
1765
1766
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1767
        $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName);
1768
1769
        if (method_exists($this, 'after_createForeignIndex_callback')) {
1770
            $this->{'after_createForeignIndex_callback'}();
1771
        }
1772
1773
        self::$logger->debug('<<createForeignIndex');
1774
    }
1775
1776
    /**
1777
     * Creates a unique index in the database on the given attribute(s).
1778
     *
1779
     * @param string $attribute1Name The first attribute to mark unique in the database.
1780
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1781
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1782
     *
1783
     * @since 1.0
1784
     *
1785
     * @throws \Alpha\Exception\FailedIndexCreateException
1786
     */
1787
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1788
    {
1789
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1790
1791
        if (method_exists($this, 'before_createUniqueIndex_callback')) {
1792
            $this->{'before_createUniqueIndex_callback'}();
1793
        }
1794
1795
        $config = ConfigProvider::getInstance();
1796
1797
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
1798
        $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
1799
1800
        if (method_exists($this, 'after_createUniqueIndex_callback')) {
1801
            $this->{'before_createUniqueIndex_callback'}();
1802
        }
1803
1804
        self::$logger->debug('<<createUniqueIndex');
1805
    }
1806
1807
    /**
1808
     * Gets the data labels array.
1809
     *
1810
     * @return array An array of attribute labels.
1811
     *
1812
     * @since 1.0
1813
     */
1814
    public function getDataLabels()
1815
    {
1816
        self::$logger->debug('>>getDataLabels()');
1817
        self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
1818
1819
        return $this->dataLabels;
1820
    }
1821
1822
    /**
1823
     * Sets the data labels array.
1824
     *
1825
     * @param array $labels
1826
     *
1827
     * @throws \Alpha\Exception\IllegalArguementException
1828
     *
1829
     * @since 1.2
1830
     */
1831
    public function setDataLabels($labels)
1832
    {
1833
        self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
1834
1835
        if (is_array($labels)) {
1836
            $this->dataLabels = $labels;
1837
        } else {
1838
            throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
1839
        }
1840
1841
        self::$logger->debug('<<setDataLabels()');
1842
    }
1843
1844
    /**
1845
     * Gets the data label for the given attribute name.
1846
     *
1847
     * @param $att The attribute name to get the label for.
1848
     *
1849
     * @return string
1850
     *
1851
     * @since 1.0
1852
     *
1853
     * @throws \Alpha\Exception\IllegalArguementException
1854
     */
1855
    public function getDataLabel($att)
1856
    {
1857
        self::$logger->debug('>>getDataLabel(att=['.$att.'])');
1858
1859
        if (in_array($att, array_keys($this->dataLabels))) {
1860
            self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
1861
1862
            return $this->dataLabels[$att];
1863
        } else {
1864
            self::$logger->debug('<<getDataLabel');
1865
            throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
1866
        }
1867
    }
1868
1869
    /**
1870
     * Loops over the core and custom Record directories and builds an array of all of the Record class names in the system.
1871
     *
1872
     * @return array An array of business object class names.
1873
     *
1874
     * @since 1.0
1875
     */
1876
    public static function getRecordClassNames()
1877
    {
1878
        if (self::$logger == null) {
1879
            self::$logger = new Logger('ActiveRecord');
1880
        }
1881
        self::$logger->debug('>>getRecordClassNames()');
1882
1883
        $config = ConfigProvider::getInstance();
1884
1885
        $classNameArray = array();
1886
1887
        if (file_exists($config->get('app.root').'src/Model')) { // it is possible it has not been created yet...
1888
            // first get any custom records
1889
            $handle = opendir($config->get('app.root').'src/Model');
1890
1891
            // loop over the business object directory
1892
            while (false !== ($file = readdir($handle))) {
1893
                if (preg_match('/.php/', $file)) {
1894
                    $classname = 'Model\\'.mb_substr($file, 0, -4);
1895
1896
                    if (class_exists($classname)) {
1897
                        array_push($classNameArray, $classname);
1898
                    }
1899
                }
1900
            }
1901
        }
1902
1903
        // now loop over the core records provided with Alpha
1904
        if (file_exists($config->get('app.root').'Alpha/Model')) {
1905
            $handle = opendir($config->get('app.root').'Alpha/Model');
1906
        } elseif ($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model') {
1907
            $handle = opendir($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model');
1908
        }
1909
1910
        // loop over the business object directory
1911
        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...
1912
            if (preg_match('/.php/', $file)) {
1913
                $classname = 'Alpha\\Model\\'.mb_substr($file, 0, -4);
1914
1915
                if (class_exists($classname) && substr($classname, 0, 24) != 'Alpha\\Model\\ActiveRecord') {
1916
                    array_push($classNameArray, $classname);
1917
                }
1918
            }
1919
        }
1920
1921
        asort($classNameArray);
1922
        self::$logger->debug('<<getRecordClassNames ['.var_export($classNameArray, true).']');
1923
1924
        return $classNameArray;
1925
    }
1926
1927
    /**
1928
     * Get the array of default attribute names.
1929
     *
1930
     * @return array An array of attribute names.
1931
     *
1932
     * @since 1.0
1933
     */
1934
    public function getDefaultAttributes()
1935
    {
1936
        self::$logger->debug('>>getDefaultAttributes()');
1937
        self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
1938
1939
        return $this->defaultAttributes;
1940
    }
1941
1942
    /**
1943
     * Get the array of transient attribute names.
1944
     *
1945
     * @return array An array of attribute names.
1946
     *
1947
     * @since 1.0
1948
     */
1949
    public function getTransientAttributes()
1950
    {
1951
        self::$logger->debug('>>getTransientAttributes()');
1952
        self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
1953
1954
        return $this->transientAttributes;
1955
    }
1956
1957
    /**
1958
     * Get the array of persistent attribute names, i.e. those that are saved in the database.
1959
     *
1960
     * @return array An array of attribute names.
1961
     *
1962
     * @since 1.0
1963
     */
1964
    public function getPersistentAttributes()
1965
    {
1966
        self::$logger->debug('>>getPersistentAttributes()');
1967
1968
        $attributes = array();
1969
1970
        // get the class attributes
1971
        $reflection = new ReflectionClass(get_class($this));
1972
        $properties = $reflection->getProperties();
1973
1974
        foreach ($properties as $propObj) {
1975
            $propName = $propObj->name;
1976
1977
            // filter transient attributes
1978
            if (!in_array($propName, $this->transientAttributes)) {
1979
                array_push($attributes, $propName);
1980
            }
1981
        }
1982
1983
        self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
1984
1985
        return $attributes;
1986
    }
1987
1988
    /**
1989
     * Setter for the Object ID (OID).
1990
     *
1991
     * @param int $OID The Object ID.
1992
     *
1993
     * @since 1.0
1994
     */
1995
    public function setOID($OID)
1996
    {
1997
        self::$logger->debug('>>setOID(OID=['.$OID.'])');
1998
        self::$logger->debug('<<setOID');
1999
        $this->OID = $OID;
2000
    }
2001
2002
    /**
2003
     * Inspector to see if the business object is transient (not presently stored in the database).
2004
     *
2005
     * @return bool
2006
     *
2007
     * @since 1.0
2008
     */
2009
    public function isTransient()
2010
    {
2011
        self::$logger->debug('>>isTransient()');
2012
2013
        if (empty($this->OID) || !isset($this->OID) || $this->OID == '00000000000') {
2014
            self::$logger->debug('<<isTransient [true]');
2015
2016
            return true;
2017
        } else {
2018
            self::$logger->debug('<<isTransient [false]');
2019
2020
            return false;
2021
        }
2022
    }
2023
2024
    /**
2025
     * Get the last database query run on this object.
2026
     *
2027
     * @return string An SQL query string.
2028
     *
2029
     * @since 1.0
2030
     */
2031
    public function getLastQuery()
2032
    {
2033
        self::$logger->debug('>>getLastQuery()');
2034
        self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
2035
2036
        return $this->lastQuery;
2037
    }
2038
2039
    /**
2040
     * Unsets all of the attributes of this object to null.
2041
     *
2042
     * @since 1.0
2043
     */
2044
    private function clear()
2045
    {
2046
        self::$logger->debug('>>clear()');
2047
2048
        // get the class attributes
2049
        $reflection = new ReflectionClass(get_class($this));
2050
        $properties = $reflection->getProperties();
2051
2052
        foreach ($properties as $propObj) {
2053
            $propName = $propObj->name;
2054
            if (!$propObj->isPrivate()) {
2055
                unset($this->$propName);
2056
            }
2057
        }
2058
2059
        self::$logger->debug('<<clear');
2060
    }
2061
2062
    /**
2063
     * Reloads the object from the database, overwritting any attribute values in memory.
2064
     *
2065
     * @since 1.0
2066
     *
2067
     * @throws \Alpha\Exception\AlphaException
2068
     */
2069
    public function reload()
2070
    {
2071
        self::$logger->debug('>>reload()');
2072
2073
        if (!$this->isTransient()) {
2074
            $this->load($this->getOID());
2075
        } else {
2076
            throw new AlphaException('Cannot reload transient object from database!');
2077
        }
2078
        self::$logger->debug('<<reload');
2079
    }
2080
2081
    /**
2082
     * Loads the definition from the file system for the Record class name provided.
2083
     *
2084
     * @param string $classname The name of the business object class name.
2085
     *
2086
     * @since 1.0
2087
     *
2088
     * @throws \Alpha\Exception\IllegalArguementException
2089
     *
2090
     * @deprecated Use autoloader!
2091
     */
2092
    public static function loadClassDef($classname)
2093
    {
2094
        if (self::$logger == null) {
2095
            self::$logger = new Logger('ActiveRecord');
2096
        }
2097
        self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
2098
2099
        $config = ConfigProvider::getInstance();
2100
2101
        if (file_exists($config->get('app.root').'Model/'.$classname.'.php')) {
2102
            require_once $config->get('app.root').'Model/'.$classname.'.php';
2103
        } elseif (file_exists($config->get('app.root').'alpha/Alpha/Model/'.$classname.'.php')) {
2104
            require_once $config->get('app.root').'alpha/Alpha/Model/'.$classname.'.php';
2105
        } elseif (file_exists($config->get('app.root').'alpha/Alpha/Model/Types/'.$classname.'.php')) {
2106
            require_once $config->get('app.root').'alpha/Alpha/Model/Types/'.$classname.'.php';
2107
        } else {
2108
            throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
2109
        }
2110
2111
        self::$logger->debug('<<loadClassDef');
2112
    }
2113
2114
    /**
2115
     * Checks that a record exists for the Record in the database.
2116
     *
2117
     * @param int $OID The Object ID of the object we want to see whether it exists or not.
2118
     *
2119
     * @return bool
2120
     *
2121
     * @since 1.0
2122
     *
2123
     * @throws \Alpha\Exception\AlphaException
2124
     */
2125
    public function checkRecordExists($OID)
2126
    {
2127
        self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
2128
2129
        if (method_exists($this, 'before_checkRecordExists_callback')) {
2130
            $this->{'before_checkRecordExists_callback'}();
2131
        }
2132
2133
        $config = ConfigProvider::getInstance();
2134
2135
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
2136
        $recordExists = $provider->checkRecordExists($OID);
2137
2138
        if (method_exists($this, 'after_checkRecordExists_callback')) {
2139
            $this->{'after_checkRecordExists_callback'}();
2140
        }
2141
2142
        self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
2143
2144
        return $recordExists;
2145
    }
2146
2147
    /**
2148
     * Checks to see if the table name matches the classname, and if not if the table
2149
     * name matches the classname name of another record, i.e. the table is used to store
2150
     * multiple types of records.
2151
     *
2152
     * @return bool
2153
     *
2154
     * @since 1.0
2155
     *
2156
     * @throws \Alpha\Exception\BadTableNameException
2157
     */
2158
    public function isTableOverloaded()
2159
    {
2160
        self::$logger->debug('>>isTableOverloaded()');
2161
2162
        $config = ConfigProvider::getInstance();
2163
2164
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $this);
2165
        $isOverloaded = $provider->isTableOverloaded();
2166
2167
        self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
2168
2169
        return $isOverloaded;
2170
    }
2171
2172
    /**
2173
     * Starts a new database transaction.
2174
     *
2175
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2176
     *
2177
     * @since 1.0
2178
     *
2179
     * @throws \Alpha\Exception\AlphaException
2180
     */
2181
    public static function begin($Record = null)
2182
    {
2183
        if (self::$logger == null) {
2184
            self::$logger = new Logger('ActiveRecord');
2185
        }
2186
        self::$logger->debug('>>begin()');
2187
2188
        $config = ConfigProvider::getInstance();
2189
2190
        if (isset($Record)) {
2191
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2192
        } else {
2193
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2194
        }
2195
2196
        try {
2197
            $provider->begin();
2198
        } catch (\Exception $e) {
2199
            throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
2200
        }
2201
2202
        self::$logger->debug('<<begin');
2203
    }
2204
2205
    /**
2206
     * Commits the current database transaction.
2207
     *
2208
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2209
     *
2210
     * @since 1.0
2211
     *
2212
     * @throws \Alpha\Exception\FailedSaveException
2213
     */
2214
    public static function commit($Record = null)
2215
    {
2216
        if (self::$logger == null) {
2217
            self::$logger = new Logger('ActiveRecord');
2218
        }
2219
        self::$logger->debug('>>commit()');
2220
2221
        $config = ConfigProvider::getInstance();
2222
2223
        if (isset($Record)) {
2224
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2225
        } else {
2226
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2227
        }
2228
2229
        try {
2230
            $provider->commit();
2231
        } catch (\Exception $e) {
2232
            throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
2233
        }
2234
2235
        self::$logger->debug('<<commit');
2236
    }
2237
2238
    /**
2239
     * Aborts the current database transaction.
2240
     *
2241
     * @param $Record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2242
     *
2243
     * @since 1.0
2244
     *
2245
     * @throws \Alpha\Exception\AlphaException
2246
     */
2247
    public static function rollback($Record = null)
2248
    {
2249
        if (self::$logger == null) {
2250
            self::$logger = new Logger('ActiveRecord');
2251
        }
2252
        self::$logger->debug('>>rollback()');
2253
2254
        $config = ConfigProvider::getInstance();
2255
2256
        if (isset($Record)) {
2257
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), $Record);
2258
        } else {
2259
            $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2260
        }
2261
2262
        try {
2263
            $provider->rollback();
2264
        } catch (\Exception $e) {
2265
            throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
2266
        }
2267
2268
        self::$logger->debug('<<rollback');
2269
    }
2270
2271
    /**
2272
     * Static method that tries to determine if the system database has been installed or not.
2273
     *
2274
     * @return bool
2275
     *
2276
     * @since 1.0
2277
     */
2278
    public static function isInstalled()
2279
    {
2280
        if (self::$logger == null) {
2281
            self::$logger = new Logger('ActiveRecord');
2282
        }
2283
        self::$logger->debug('>>isInstalled()');
2284
2285
        $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...
2286
2287
        /*
2288
         * Install conditions are:
2289
         *
2290
         * 1. person table exists
2291
         * 2. rights table exists
2292
         */
2293
        if (self::checkRecordTableExists('Alpha\Model\Person') && self::checkRecordTableExists('Alpha\Model\Rights')) {
2294
            self::$logger->debug('<<isInstalled [true]');
2295
2296
            return true;
2297
        } else {
2298
            self::$logger->debug('<<isInstalled [false]');
2299
2300
            return false;
2301
        }
2302
    }
2303
2304
    /**
2305
     * Returns true if the Record has a Relation property called tags, false otherwise.
2306
     *
2307
     * @return bool
2308
     *
2309
     * @since 1.0
2310
     */
2311
    public function isTagged()
2312
    {
2313
        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...
2314
            return true;
2315
        } else {
2316
            return false;
2317
        }
2318
    }
2319
2320
    /**
2321
     * Returns the contents of the taggedAttributes array, or an empty array if that does not exist.
2322
     *
2323
     * @return array
2324
     *
2325
     * @since 1.2.3
2326
     */
2327
    public function getTaggedAttributes()
2328
    {
2329
        if ($this->isTagged()) {
2330
            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...
2331
        } else {
2332
            return array();
2333
        }
2334
    }
2335
2336
    /**
2337
     * Setter for the Record version number.
2338
     *
2339
     * @param int $versionNumber The version number.
2340
     *
2341
     * @since 1.0
2342
     */
2343
    private function setVersion($versionNumber)
2344
    {
2345
        $this->version_num->setValue($versionNumber);
2346
    }
2347
2348
    /**
2349
     * Cast a Record to another type of record.  A new Record will be returned with the same OID and
2350
     * version_num as the old record, so this is NOT a true cast but is a copy.  All attribute
2351
     * values will be copied accross.
2352
     *
2353
     * @param string                    $targetClassName     The fully-qualified name of the target Record class.
2354
     * @param \Alpha\Model\ActiveRecord $originalRecord      The original business object.
2355
     *
2356
     * @return \Alpha\Model\ActiveRecord The new business object resulting from the cast.
2357
     *
2358
     * @since 1.0
2359
     */
2360
    public function cast($targetClassName, $originalRecord)
2361
    {
2362
        $Record = new $targetClassName();
2363
        $Record->setOID($originalRecord->getOID());
2364
        $Record->setVersion($originalRecord->getVersion());
2365
2366
        // get the class attributes
2367
        $originalRecordreflection = new ReflectionClass(get_class($originalRecord));
2368
        $originalRecordproperties = $originalRecordreflection->getProperties();
2369
        $newRecordreflection = new ReflectionClass($targetClassName);
2370
        $newRecordproperties = $newRecordreflection->getProperties();
2371
2372
        // copy the property values from the old Record to the new record
2373
2374
        if (count($originalRecordproperties) < count($newRecordproperties)) {
2375
            // the original Record is smaller, so loop over its properties
2376
            foreach ($originalRecordproperties as $propObj) {
2377
                $propName = $propObj->name;
2378
                if (!in_array($propName, $this->transientAttributes)) {
2379
                    $Record->set($propName, $originalRecord->get($propName));
2380
                }
2381
            }
2382
        } else {
2383
            // the new Record is smaller, so loop over its properties
2384
            foreach ($newRecordproperties as $propObj) {
2385
                $propName = $propObj->name;
2386
                if (!in_array($propName, $this->transientAttributes)) {
2387
                    $Record->set($propName, $originalRecord->get($propName));
2388
                }
2389
            }
2390
        }
2391
2392
        return $Record;
2393
    }
2394
2395
    /**
2396
     * Returns the simple class name, stripped of the namespace.
2397
     *
2398
     * @return string
2399
     *
2400
     * @since 1.0
2401
     */
2402
    public function getFriendlyClassName()
2403
    {
2404
        $reflectClass = new ReflectionClass($this);
2405
2406
        return $reflectClass->getShortname();
2407
    }
2408
2409
    /**
2410
     * Check to see if an attribute exists on the record.
2411
     *
2412
     * @param $attribute The attribute name.
2413
     *
2414
     * @return bool
2415
     *
2416
     * @since 1.0
2417
     */
2418
    public function hasAttribute($attribute)
2419
    {
2420
        try {
2421
            $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...
2422
2423
            return true;
2424
        } 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...
2425
            return false;
2426
        }
2427
    }
2428
2429
    /**
2430
     * Stores the business object to the configured cache instance.
2431
     *
2432
     * @since 1.1
2433
     */
2434
    public function addToCache()
2435
    {
2436
        self::$logger->debug('>>addToCache()');
2437
        $config = ConfigProvider::getInstance();
2438
2439
        try {
2440
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2441
            $cache->set(get_class($this).'-'.$this->getOID(), $this, 3600);
2442
        } catch (\Exception $e) {
2443
            self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').'] 
2444
                instance: ['.$e->getMessage().']');
2445
        }
2446
2447
        self::$logger->debug('<<addToCache');
2448
    }
2449
2450
    /**
2451
     * Removes the business object from the configured cache instance.
2452
     *
2453
     * @since 1.1
2454
     */
2455
    public function removeFromCache()
2456
    {
2457
        self::$logger->debug('>>removeFromCache()');
2458
        $config = ConfigProvider::getInstance();
2459
2460
        try {
2461
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2462
            $cache->delete(get_class($this).'-'.$this->getOID());
2463
        } catch (\Exception $e) {
2464
            self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
2465
                instance: ['.$e->getMessage().']');
2466
        }
2467
2468
        self::$logger->debug('<<removeFromCache');
2469
    }
2470
2471
    /**
2472
     * Attempts to load the business object from the configured cache instance.
2473
     *
2474
     * @since 1.1
2475
     *
2476
     * @return bool
2477
     */
2478
    public function loadFromCache()
2479
    {
2480
        self::$logger->debug('>>loadFromCache()');
2481
        $config = ConfigProvider::getInstance();
2482
2483
        try {
2484
            $cache = CacheProviderFactory::getInstance($config->get('cache.provider.name'));
2485
            $Record = $cache->get(get_class($this).'-'.$this->getOID());
2486
2487
            if (!$Record) {
2488
                self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getOID().']');
2489
                self::$logger->debug('<<loadFromCache: [false]');
2490
2491
                return false;
2492
            } else {
2493
                // get the class attributes
2494
                $reflection = new ReflectionClass(get_class($this));
2495
                $properties = $reflection->getProperties();
2496
2497
                foreach ($properties as $propObj) {
2498
                    $propName = $propObj->name;
2499
2500
                    // filter transient attributes
2501
                    if (!in_array($propName, $this->transientAttributes)) {
2502
                        $this->set($propName, $Record->get($propName, true));
2503
                    } elseif (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
2504
                        $prop = $this->getPropObject($propName);
2505
2506
                        // handle the setting of ONE-TO-MANY relation values
2507
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
2508
                            $this->set($propObj->name, $this->getOID());
2509
                        }
2510
                    }
2511
                }
2512
2513
                self::$logger->debug('<<loadFromCache: [true]');
2514
2515
                return true;
2516
            }
2517
        } catch (\Exception $e) {
2518
            self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
2519
             instance: ['.$e->getMessage().']');
2520
2521
            self::$logger->debug('<<loadFromCache: [false]');
2522
2523
            return false;
2524
        }
2525
    }
2526
2527
    /**
2528
     * Sets the last query executed on this business object.
2529
     *
2530
     * @param string $query
2531
     *
2532
     * @since 1.1
2533
     */
2534
    public function setLastQuery($query)
2535
    {
2536
        self::$logger->sql($query);
2537
        $this->lastQuery = $query;
2538
    }
2539
2540
    /**
2541
     * Re-initialize the static logger property on the Record after de-serialize, as PHP does
2542
     * not serialize static properties.
2543
     *
2544
     * @since 1.2
2545
     */
2546
    public function __wakeup()
2547
    {
2548
        if (self::$logger == null) {
2549
            self::$logger = new Logger(get_class($this));
2550
        }
2551
    }
2552
2553
    /**
2554
     * Sets maintainHistory attribute on this DAO.
2555
     *
2556
     * @param bool $maintainHistory
2557
     *
2558
     * @throws \Alpha\Exception\IllegalArguementException
2559
     *
2560
     * @since 1.2
2561
     */
2562
    public function setMaintainHistory($maintainHistory)
2563
    {
2564
        if (!is_bool($maintainHistory)) {
2565
            throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
2566
        }
2567
2568
        $this->maintainHistory = $maintainHistory;
2569
    }
2570
2571
    /**
2572
     * Gets the value of the  maintainHistory attribute.
2573
     *
2574
     * @return bool
2575
     *
2576
     * @since 1.2
2577
     */
2578
    public function getMaintainHistory()
2579
    {
2580
        return $this->maintainHistory;
2581
    }
2582
2583
    /**
2584
     * Return a hash array of the object containing attribute names and simplfied values.
2585
     *
2586
     * @return array
2587
     *
2588
     * @since  1.2.4
2589
     */
2590
    public function toArray()
2591
    {
2592
        // get the class attributes
2593
        $reflection = new ReflectionClass(get_class($this));
2594
        $properties = $reflection->getProperties();
2595
2596
        $propArray = array();
2597
2598
        foreach ($properties as $propObj) {
2599
            $propName = $propObj->name;
2600
2601
            if (!in_array($propName, $this->transientAttributes)) {
2602
                $val = $this->get($propName);
2603
2604
                if (is_object($val)) {
2605
                    $val = $val->getValue();
2606
                }
2607
2608
                $propArray[$propName] = $val;
2609
            }
2610
        }
2611
2612
        return $propArray;
2613
    }
2614
2615
    /**
2616
     * Check to see if the configured database exists.
2617
     *
2618
     * @return bool
2619
     *
2620
     * @since 2.0
2621
     */
2622
    public static function checkDatabaseExists()
2623
    {
2624
        $config = ConfigProvider::getInstance();
2625
2626
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2627
2628
        return $provider->checkDatabaseExists();
2629
    }
2630
2631
    /**
2632
     * Creates the configured database.
2633
     *
2634
     * @throws \Alpha\Exception\AlphaException
2635
     *
2636
     * @since 2.0
2637
     */
2638
    public static function createDatabase()
2639
    {
2640
        $config = ConfigProvider::getInstance();
2641
2642
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2643
        $provider->createDatabase();
2644
    }
2645
2646
    /**
2647
     * Drops the configured database.
2648
     *
2649
     * @throws \Alpha\Exception\AlphaException
2650
     *
2651
     * @since 2.0
2652
     */
2653
    public static function dropDatabase()
2654
    {
2655
        $config = ConfigProvider::getInstance();
2656
2657
        $provider = ActiveRecordProviderFactory::getInstance($config->get('db.provider.name'), new Person());
2658
        $provider->dropDatabase();
2659
    }
2660
}
2661