ActiveRecord::loadAllByAttribute()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
cc 3
nc 4
nop 8

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Date;
6
use Alpha\Model\Type\Integer;
7
use Alpha\Model\Type\Timestamp;
8
use Alpha\Model\Type\TypeInterface;
9
use Alpha\Model\Type\Relation;
10
use Alpha\Model\Type\RelationLookup;
11
use Alpha\Util\Config\ConfigProvider;
12
use Alpha\Util\Logging\Logger;
13
use Alpha\Util\Service\ServiceFactory;
14
use Alpha\Exception\AlphaException;
15
use Alpha\Exception\FailedSaveException;
16
use Alpha\Exception\FailedDeleteException;
17
use Alpha\Exception\ValidationException;
18
use Alpha\Exception\RecordNotFoundException;
19
use Alpha\Exception\IllegalArguementException;
20
use Alpha\Exception\LockingException;
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) 2018, John Collins (founder of Alpha Framework).
33
 * All rights reserved.
34
 *
35
 * <pre>
36
 * Redistribution and use in source and binary forms, with or
37
 * without modification, are permitted provided that the
38
 * following conditions are met:
39
 *
40
 * * Redistributions of source code must retain the above
41
 *   copyright notice, this list of conditions and the
42
 *   following disclaimer.
43
 * * Redistributions in binary form must reproduce the above
44
 *   copyright notice, this list of conditions and the
45
 *   following disclaimer in the documentation and/or other
46
 *   materials provided with the distribution.
47
 * * Neither the name of the Alpha Framework nor the names
48
 *   of its contributors may be used to endorse or promote
49
 *   products derived from this software without specific
50
 *   prior written permission.
51
 *
52
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
53
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
54
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
55
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
56
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
57
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
58
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
59
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
62
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
63
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65
 * </pre>
66
 */
67
abstract class ActiveRecord
68
{
69
    /**
70
     * The object ID.
71
     *
72
     * @var int
73
     *
74
     * @since 1.0
75
     */
76
    protected $ID;
77
78
    /**
79
     * The last database query run by this object.  Useful for tracing an error.
80
     *
81
     * @var string
82
     *
83
     * @since 1.0
84
     */
85
    protected $lastQuery;
86
87
    /**
88
     * The version number of the object, used for locking mechanism.
89
     *
90
     * @var \Alpha\Model\Type\Integer
91
     *
92
     * @since 1.0
93
     */
94
    protected $version_num;
95
96
    /**
97
     * The timestamp of creation.
98
     *
99
     * @var \Alpha\Model\Type\Timestamp
100
     *
101
     * @since 1.0
102
     */
103
    protected $created_ts;
104
105
    /**
106
     * The ID of the person who created this record.
107
     *
108
     * @var \Alpha\Model\Type\Integer
109
     *
110
     * @since 1.0
111
     */
112
    protected $created_by;
113
114
    /**
115
     * The timestamp of the last update.
116
     *
117
     * @var \Alpha\Model\Type\Timestamp
118
     *
119
     * @since 1.0
120
     */
121
    protected $updated_ts;
122
123
    /**
124
     * The ID of the person who last updated this record.
125
     *
126
     * @var \Alpha\Model\Type\Integer
127
     *
128
     * @since 1.0
129
     */
130
    protected $updated_by;
131
132
    /**
133
     * An array of the names of all of the default attributes of a persistent Record defined in this class.
134
     *
135
     * @var array
136
     *
137
     * @since 1.0
138
     */
139
    protected $defaultAttributes = array('ID', 'lastQuery', 'version_num', 'dataLabels', 'created_ts', 'created_by', 'updated_ts', 'updated_by', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
140
141
    /**
142
     * An array of the names of all of the transient attributes of a persistent Record which are not saved to the DB.
143
     *
144
     * @var array
145
     *
146
     * @since 1.0
147
     */
148
    protected $transientAttributes = array('lastQuery', 'dataLabels', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
149
150
    /**
151
     * An array of the uniquely-constained attributes of this persistent record.
152
     *
153
     * @var array
154
     *
155
     * @since 1.0
156
     */
157
    protected $uniqueAttributes = array();
158
159
    /**
160
     * An array of the data labels used for displaying class attributes.
161
     *
162
     * @var array
163
     *
164
     * @since 1.0
165
     */
166
    protected $dataLabels = array();
167
168
    /**
169
     * Trace logger.
170
     *
171
     * @var \Alpha\Util\Logging\Logger
172
     *
173
     * @since 1.0
174
     */
175
    private static $logger = null;
176
177
    /**
178
     * Determines if we will maintain a _history table for this record (default is false).
179
     *
180
     * @var bool
181
     *
182
     * @since 1.2
183
     */
184
    private $maintainHistory = false;
185
186
    /**
187
     * The constructor which sets up some housekeeping attributes.
188
     *
189
     * @since 1.0
190
     */
191
    public function __construct()
192
    {
193
        self::$logger = new Logger('ActiveRecord');
194
        self::$logger->debug('>>__construct()');
195
196
        $config = ConfigProvider::getInstance();
197
        $sessionProvider = $config->get('session.provider.name');
198
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
199
200
        set_exception_handler('Alpha\Util\ErrorHandlers::catchException');
201
        set_error_handler('Alpha\Util\ErrorHandlers::catchError', $config->get('php.error.log.level'));
202
203
        $this->version_num = new Integer(0);
204
        $this->created_ts = new Timestamp(date('Y-m-d H:i:s'));
205
        $person_ID = ($session->get('currentUser') != null ? $session->get('currentUser')->getID() : 0);
206
        $this->created_by = new Integer($person_ID);
207
        $this->updated_ts = new Timestamp(date('Y-m-d H:i:s'));
208
        $this->updated_by = new Integer($person_ID);
209
210
        self::$logger->debug('<<__construct');
211
    }
212
213
    /**
214
     * Disconnects the current database connection if one exists.
215
     *
216
     * @since 1.0
217
     */
218
    public static function disconnect()
219
    {
220
        $config = ConfigProvider::getInstance();
221
222
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
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 = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
244
        $provider->setRecord($this);
245
        $result = $provider->query($sqlQuery);
246
247
        self::$logger->debug('<<query ['.print_r($result, true).']');
248
249
        return $result;
250
    }
251
252
    /**
253
     * Populates the child object with the properties retrived from the database for the object $ID.
254
     *
255
     * @param int $ID     The object ID of the business object to load.
256
     * @param int $version Optionaly, provide the version to load that version from the [tablename]_history table.
257
     *
258
     * @since 1.0
259
     *
260
     * @throws \Alpha\Exception\RecordNotFoundException
261
     */
262
    public function load($ID, $version = 0)
263
    {
264
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
265
266
        if (method_exists($this, 'before_load_callback')) {
267
            $this->{'before_load_callback'}();
268
        }
269
270
        $config = ConfigProvider::getInstance();
271
272
        $this->ID = $ID;
273
274
        if ($config->get('cache.provider.name') != '' && $this->loadFromCache()) {
275
            // Record was found in cache
276
        } else {
277
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
278
            $provider->setRecord($this);
279
            $provider->load($ID, $version);
280
281
            if ($config->get('cache.provider.name') != '') {
282
                $this->addToCache();
283
            }
284
        }
285
286
        $this->setEnumOptions();
287
288
        if (method_exists($this, 'after_load_callback')) {
289
            $this->{'after_load_callback'}();
290
        }
291
292
        self::$logger->debug('<<load');
293
    }
294
295
    /**
296
     * Load all old versions (if any) of this record from the [tablename]_history table.
297
     *
298
     * @param int $ID The object ID of the record to load.
299
     *
300
     * @return array An array containing objects of this type of record object, order by version.
301
     *
302
     * @since 2.0
303
     *
304
     * @throws \Alpha\Exception\RecordFoundException
305
     */
306
    public function loadAllOldVersions($ID)
307
    {
308
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
309
310
        $config = ConfigProvider::getInstance();
311
312
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
313
        $provider->setRecord($this);
314
        $objects = $provider->loadAllOldVersions($ID);
315
316
        self::$logger->debug('<<loadAllOldVersions['.count($objects).']');
317
318
        return $objects;
319
    }
320
321
    /**
322
     * Populates the child object from the database table by the given attribute value.
323
     *
324
     * @param string $attribute       The name of the attribute to load the object by.
325
     * @param string $value           The value of the attribute to load the object by.
326
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
327
     * @param array  $loadAttributes  The attributes to load from the database to this object (leave blank to load all attributes)
328
     *
329
     * @since 1.0
330
     *
331
     * @throws \Alpha\Exception\RecordNotFoundException
332
     */
333
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
334
    {
335
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
336
            loadAttributes=['.var_export($loadAttributes, true).'])');
337
338
        if (method_exists($this, 'before_loadByAttribute_callback')) {
339
            $this->{'before_loadByAttribute_callback'}();
340
        }
341
342
        $config = ConfigProvider::getInstance();
343
344
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
345
        $provider->setRecord($this);
346
        $provider->loadByAttribute($attribute, $value, $ignoreClassType, $loadAttributes);
347
348
        $this->setEnumOptions();
349
350
        if ($config->get('cache.provider.name') != '' && count($loadAttributes) == 0) { // we will only cache fully-populated records
351
            $this->addToCache();
352
        }
353
354
        if (method_exists($this, 'after_loadByAttribute_callback')) {
355
            $this->{'after_loadByAttribute_callback'}();
356
        }
357
358
        self::$logger->debug('<<loadByAttribute');
359
    }
360
361
    /**
362
     * Loads all of the objects of this class into an array which is returned.
363
     *
364
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
365
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
366
     * @param string $orderBy         The name of the field to sort the objects by.
367
     * @param string $order           The order to sort the objects by.
368
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
369
     *
370
     * @return array An array containing objects of this type of business object.
371
     *
372
     * @since 1.0
373
     *
374
     * @throws \Alpha\Exception\RecordNotFoundException
375
     */
376
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
377
    {
378
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
379
380
        if (method_exists($this, 'before_loadAll_callback')) {
381
            $this->{'before_loadAll_callback'}();
382
        }
383
384
        $config = ConfigProvider::getInstance();
385
386
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
387
        $provider->setRecord($this);
388
        $objects = $provider->loadAll($start, $limit, $orderBy, $order, $ignoreClassType);
389
390
        if (method_exists($this, 'after_loadAll_callback')) {
391
            $this->{'after_loadAll_callback'}();
392
        }
393
394
        self::$logger->debug('<<loadAll ['.count($objects).']');
395
396
        return $objects;
397
    }
398
399
    /**
400
     * Loads all of the objects of this class by the specified attribute into an array which is returned.
401
     *
402
     * @param string $attribute       The attribute to load the objects by.
403
     * @param string $value           The value of the attribute to load the objects by.
404
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
405
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
406
     * @param string $orderBy         The name of the field to sort the objects by.
407
     * @param string $order           The order to sort the objects by.
408
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
409
     * @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.
410
     *
411
     * @return array An array containing objects of this type of business object.
412
     *
413
     * @since 1.0
414
     *
415
     * @throws \Alpha\Exception\RecordNotFoundException
416
     * @throws \Alpha\Exception\IllegalArguementException
417
     */
418
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
419
    {
420
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
421
422
        if (method_exists($this, 'before_loadAllByAttribute_callback')) {
423
            $this->{'before_loadAllByAttribute_callback'}();
424
        }
425
426
        $config = ConfigProvider::getInstance();
427
428
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
429
        $provider->setRecord($this);
430
        $objects = $provider->loadAllByAttribute($attribute, $value, $start, $limit, $orderBy, $order, $ignoreClassType);
431
432
        if (method_exists($this, 'after_loadAllByAttribute_callback')) {
433
            $this->{'after_loadAllByAttribute_callback'}();
434
        }
435
436
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
437
438
        return $objects;
439
    }
440
441
    /**
442
     * Loads all of the objects of this class by the specified attributes into an array which is returned.
443
     *
444
     * @param array  $attributes      The attributes to load the objects by.
445
     * @param array  $values          The values of the attributes to load the objects by.
446
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
447
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
448
     * @param string $orderBy         The name of the field to sort the objects by.
449
     * @param string $order           The order to sort the objects by.
450
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
451
     *
452
     * @return array An array containing objects of this type of business object.
453
     *
454
     * @since 1.0
455
     *
456
     * @throws \Alpha\Exception\RecordNotFoundException
457
     * @throws \Alpha\Exception\IllegalArguementException
458
     */
459
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
460
    {
461
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
462
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
463
464
        if (method_exists($this, 'before_loadAllByAttributes_callback')) {
465
            $this->{'before_loadAllByAttributes_callback'}();
466
        }
467
468
        $config = ConfigProvider::getInstance();
469
470
        if (!is_array($attributes) || !is_array($values)) {
471
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).
472
                '] provided to loadAllByAttributes');
473
        }
474
475
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
476
        $provider->setRecord($this);
477
        $objects = $provider->loadAllByAttributes($attributes, $values, $start, $limit, $orderBy, $order, $ignoreClassType);
478
479
        if (method_exists($this, 'after_loadAllByAttributes_callback')) {
480
            $this->{'after_loadAllByAttributes_callback'}();
481
        }
482
483
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
484
485
        return $objects;
486
    }
487
488
    /**
489
     * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
490
     *
491
     * @param string $date            The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
492
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
493
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
494
     * @param string $orderBy         The name of the field to sort the objects by.
495
     * @param string $order           The order to sort the objects by.
496
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
497
     *
498
     * @return array An array containing objects of this type of business object.
499
     *
500
     * @since 1.0
501
     *
502
     * @throws \Alpha\Exception\RecordNotFoundException
503
     */
504
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
505
    {
506
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
507
508
        if (method_exists($this, 'before_loadAllByDayUpdated_callback')) {
509
            $this->{'before_loadAllByDayUpdated_callback'}();
510
        }
511
512
        $config = ConfigProvider::getInstance();
513
514
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
515
        $provider->setRecord($this);
516
        $objects = $provider->loadAllByDayUpdated($date, $start, $limit, $orderBy, $order, $ignoreClassType);
517
518
        if (method_exists($this, 'after_loadAllByDayUpdated_callback')) {
519
            $this->{'after_loadAllByDayUpdated_callback'}();
520
        }
521
522
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
523
524
        return $objects;
525
    }
526
527
    /**
528
     * Loads all of the specified attribute values of this class by the specified attribute into an
529
     * array which is returned.
530
     *
531
     * @param string $attribute       The attribute name to load the field values by.
532
     * @param string $value           The value of the attribute to load the field values by.
533
     * @param string $returnAttribute The name of the attribute to return.
534
     * @param string $order           The order to sort the records by.
535
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
536
     *
537
     * @return array An array of field values.
538
     *
539
     * @since 1.0
540
     *
541
     * @throws \Alpha\Exception\RecordNotFoundException
542
     */
543
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
544
    {
545
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
546
547
        $config = ConfigProvider::getInstance();
548
549
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
550
        $provider->setRecord($this);
551
        $values = $provider->loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order, $ignoreClassType);
552
553
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
554
555
        return $values;
556
    }
557
558
    /**
559
     * Saves the object.  If $this->ID is empty or null it will INSERT, otherwise UPDATE.
560
     *
561
     * @since 1.0
562
     *
563
     * @throws \Alpha\Exception\FailedSaveException
564
     * @throws \Alpha\Exception\LockingException
565
     * @throws \Alpha\Exception\ValidationException
566
     */
567
    public function save()
568
    {
569
        self::$logger->debug('>>save()');
570
571
        if (method_exists($this, 'before_save_callback')) {
572
            $this->{'before_save_callback'}();
573
        }
574
575
        $config = ConfigProvider::getInstance();
576
577
        // firstly we will validate the object before we try to save it
578
        $this->validate();
579
580
        $sessionProvider = $config->get('session.provider.name');
581
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
582
583
        if ($this->getVersion() != $this->getVersionNumber()->getValue()) {
584
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
585
        }
586
587
        // set the "updated by" fields, we can only set the user id if someone is logged in
588
        if ($session->get('currentUser') != null) {
589
            $this->set('updated_by', $session->get('currentUser')->getID());
590
        }
591
592
        $this->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
593
594
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
595
        $provider->setRecord($this);
596
        $provider->save();
597
598
        if ($config->get('cache.provider.name') != '') {
599
            $this->removeFromCache();
600
            $this->addToCache();
601
        }
602
603
        if (method_exists($this, 'after_save_callback')) {
604
            $this->{'after_save_callback'}();
605
        }
606
    }
607
608
    /**
609
     * Saves relationship values, including lookup entries, for this record.
610
     *
611
     * @since 3.0
612
     *
613
     * @throws \Alpha\Exception\FailedSaveException
614
     */
615
    public function saveRelations()
616
    {
617
        $reflection = new ReflectionClass(get_class($this));
618
        $properties = $reflection->getProperties();
619
620
        try {
621
            foreach ($properties as $propObj) {
622
                $propName = $propObj->name;
623
624
                if ($this->getPropObject($propName) instanceof Relation) {
625
                    $prop = $this->getPropObject($propName);
626
627
                    // handle the saving of MANY-TO-MANY relation values
628
                    if ($prop->getRelationType() == 'MANY-TO-MANY' && count($prop->getRelatedIDs()) > 0) {
629
                        try {
630
                            try {
631
                                // check to see if the rel is on this class
632
                                $side = $prop->getSide(get_class($this));
633
                            } catch (IllegalArguementException $iae) {
634
                                $side = $prop->getSide(get_parent_class($this));
635
                            }
636
637
                            $lookUp = $prop->getLookup();
638
639
                            // first delete all of the old RelationLookup objects for this rel
640
                            try {
641
                                if ($side == 'left') {
642
                                    $lookUp->deleteAllByAttribute('leftID', $this->getID());
643
                                } else {
644
                                    $lookUp->deleteAllByAttribute('rightID', $this->getID());
645
                                }
646
                            } catch (\Exception $e) {
647
                                throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
648
                            }
649
650
                            $IDs = $prop->getRelatedIDs();
651
652
                            if (isset($IDs) && !empty($IDs[0])) {
653
                                // now for each posted ID, create a new RelationLookup record and save
654
                                foreach ($IDs as $id) {
655
                                    $newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
656
                                    if ($side == 'left') {
657
                                        $newLookUp->set('leftID', $this->getID());
658
                                        $newLookUp->set('rightID', $id);
659
                                    } else {
660
                                        $newLookUp->set('rightID', $this->getID());
661
                                        $newLookUp->set('leftID', $id);
662
                                    }
663
                                    $newLookUp->save();
664
                                }
665
                            }
666
                        } catch (\Exception $e) {
667
                            throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
668
                        }
669
                    }
670
671
                    // handle the saving of ONE-TO-MANY relation values
672
                    if ($prop->getRelationType() == 'ONE-TO-MANY') {
673
                        $prop->setValue($this->getID());
674
                    }
675
                }
676
            }
677
        } catch (\Exception $e) {
678
            throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
679
        }
680
    }
681
682
    /**
683
     * Saves the field specified with the value supplied.  Only works for persistent records.  Note that no Alpha type
684
     * validation is performed with this method!
685
     *
686
     * @param string $attribute The name of the attribute to save.
687
     * @param mixed  $value     The value of the attribute to save.
688
     *
689
     * @since 1.0
690
     *
691
     * @throws \Alpha\Exception\IllegalArguementException
692
     * @throws \Alpha\Exception\FailedSaveException
693
     */
694
    public function saveAttribute($attribute, $value)
695
    {
696
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
697
698
        if (method_exists($this, 'before_saveAttribute_callback')) {
699
            $this->{'before_saveAttribute_callback'}();
700
        }
701
702
        $config = ConfigProvider::getInstance();
703
704
        if (!isset($this->$attribute)) {
705
            throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
706
        }
707
708
        if ($this->isTransient()) {
709
            throw new FailedSaveException('Cannot perform saveAttribute method on transient record!');
710
        }
711
712
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
713
        $provider->setRecord($this);
714
        $provider->saveAttribute($attribute, $value);
715
716
        if ($config->get('cache.provider.name') != '') {
717
            $this->removeFromCache();
718
            $this->addToCache();
719
        }
720
721
        if (method_exists($this, 'after_saveAttribute_callback')) {
722
            $this->{'after_saveAttribute_callback'}();
723
        }
724
725
        self::$logger->debug('<<saveAttribute');
726
    }
727
728
    /**
729
     * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
730
     *
731
     * @since 1.2
732
     *
733
     * @throws \Alpha\Exception\FailedSaveException
734
     */
735
    public function saveHistory()
736
    {
737
        self::$logger->debug('>>saveHistory()');
738
739
        if (method_exists($this, 'before_saveHistory_callback')) {
740
            $this->{'before_saveHistory_callback'}();
741
        }
742
743
        $config = ConfigProvider::getInstance();
744
745
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
746
        $provider->setRecord($this);
747
        $provider->saveHistory();
748
749
        if (method_exists($this, 'after_saveHistory_callback')) {
750
            $this->{'after_saveHistory_callback'}();
751
        }
752
    }
753
754
    /**
755
     * Validates the object to be saved.
756
     *
757
     * @since 1.0
758
     *
759
     * @throws \Alpha\Exception\ValidationException
760
     */
761
    protected function validate()
762
    {
763
        self::$logger->debug('>>validate()');
764
765
        if (method_exists($this, 'before_validate_callback')) {
766
            $this->{'before_validate_callback'}();
767
        }
768
769
        // get the class attributes
770
        $reflection = new ReflectionClass(get_class($this));
771
        $properties = $reflection->getProperties();
772
773
        foreach ($properties as $propObj) {
774
            $propName = $propObj->name;
775
            if (!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
776
                $propClass = new ReflectionClass($this->getPropObject($propName));
777
                $propClass = $propClass->getShortname();
778
                if (mb_strtoupper($propClass) != 'ENUM' &&
779
                mb_strtoupper($propClass) != 'DENUM' &&
780
                mb_strtoupper($propClass) != 'DENUMITEM' &&
781
                mb_strtoupper($propClass) != 'BOOLEAN') {
782
                    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\HugeText, Alpha\Model\Type\Integer, Alpha\Model\Type\LargeText, 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...
783
                        self::$logger->debug('<<validate');
784
                        throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
785
                    }
786
                }
787
            }
788
        }
789
790
        if (method_exists($this, 'after_validate_callback')) {
791
            $this->{'after_validate_callback'}();
792
        }
793
794
        self::$logger->debug('<<validate');
795
    }
796
797
    /**
798
     * Deletes the current object from the database.
799
     *
800
     * @since 1.0
801
     *
802
     * @throws \Alpha\Exception\FailedDeleteException
803
     */
804
    public function delete()
805
    {
806
        self::$logger->debug('>>delete()');
807
808
        if (method_exists($this, 'before_delete_callback')) {
809
            $this->{'before_delete_callback'}();
810
        }
811
812
        $config = ConfigProvider::getInstance();
813
814
        // get the class attributes
815
        $reflection = new ReflectionClass(get_class($this));
816
        $properties = $reflection->getProperties();
817
818
        // check for any relations on this object, then remove them to prevent orphaned data
819
        foreach ($properties as $propObj) {
820
            $propName = $propObj->name;
821
822
            if (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
823
                $prop = $this->getPropObject($propName);
824
825
                // Handle MANY-TO-MANY rels
826
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
827
                    self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
828
829
                    try {
830
                        // check to see if the rel is on this class
831
                        $side = $prop->getSide(get_class($this));
832
                    } catch (IllegalArguementException $iae) {
833
                        $side = $prop->getSide(get_parent_class($this));
834
                    }
835
836
                    self::$logger->debug('Side is ['.$side.']'.$this->getID());
837
838
                    $lookUp = $prop->getLookup();
839
                    self::$logger->debug('Lookup object['.var_export($lookUp, true).']');
840
841
                    // delete all of the old RelationLookup objects for this rel
842
                    if ($side == 'left') {
843
                        $lookUp->deleteAllByAttribute('leftID', $this->getID());
844
                    } else {
845
                        $lookUp->deleteAllByAttribute('rightID', $this->getID());
846
                    }
847
                    self::$logger->debug('...done deleting!');
848
                }
849
850
                // should set related field values to null (MySQL is doing this for us as-is)
851
                if ($prop->getRelationType() == 'ONE-TO-MANY' && !$prop->getRelatedClass() == 'Alpha\Model\Tag') {
852
                    $relatedObjects = $prop->getRelated();
853
854
                    foreach ($relatedObjects as $object) {
855
                        $object->set($prop->getRelatedClassField(), null);
856
                        $object->save();
857
                    }
858
                }
859
860
                // in the case of tags, we will always remove the related tags once the Record is deleted
861
                if ($prop->getRelationType() == 'ONE-TO-MANY' && $prop->getRelatedClass() == 'Alpha\Model\Tag') {
862
                    // just making sure that the Relation is set to current ID as its transient
863
                    $prop->setValue($this->getID());
864
                    $relatedObjects = $prop->getRelated();
865
866
                    foreach ($relatedObjects as $object) {
867
                        $object->delete();
868
                    }
869
                }
870
            }
871
        }
872
873
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
874
        $provider->setRecord($this);
875
        $provider->delete();
876
877
        if ($config->get('cache.provider.name') != '') {
878
            $this->removeFromCache();
879
        }
880
881
        if (method_exists($this, 'after_delete_callback')) {
882
            $this->{'after_delete_callback'}();
883
        }
884
885
        $this->clear();
886
        self::$logger->debug('<<delete');
887
    }
888
889
    /**
890
     * Delete all object instances from the database by the specified attribute matching the value provided.
891
     *
892
     * @param string $attribute The name of the field to delete the objects by.
893
     * @param mixed  $value     The value of the field to delete the objects by.
894
     *
895
     * @return int The number of rows deleted.
896
     *
897
     * @since 1.0
898
     *
899
     * @throws \Alpha\Exception\FailedDeleteException
900
     */
901
    public function deleteAllByAttribute($attribute, $value)
902
    {
903
        self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
904
905
        if (method_exists($this, 'before_deleteAllByAttribute_callback')) {
906
            $this->{'before_deleteAllByAttribute_callback'}();
907
        }
908
909
        try {
910
            $doomedObjects = $this->loadAllByAttribute($attribute, $value);
911
            $deletedRowCount = 0;
912
913
            foreach ($doomedObjects as $object) {
914
                $object->delete();
915
                ++$deletedRowCount;
916
            }
917
        } catch (RecordNotFoundException $bonf) {
918
            // nothing found to delete
919
            self::$logger->warn($bonf->getMessage());
920
921
            return 0;
922
        } catch (AlphaException $e) {
923
            self::$logger->debug('<<deleteAllByAttribute [0]');
924
            throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
925
        }
926
927
        if (method_exists($this, 'after_deleteAllByAttribute_callback')) {
928
            $this->{'after_deleteAllByAttribute_callback'}();
929
        }
930
931
        self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
932
933
        return $deletedRowCount;
934
    }
935
936
    /**
937
     * Gets the version_num of the object from the database (returns 0 if the Record is not saved yet).
938
     *
939
     * @return int
940
     *
941
     * @since 1.0
942
     *
943
     * @throws \Alpha\Exception\RecordNotFoundException
944
     */
945
    public function getVersion()
946
    {
947
        self::$logger->debug('>>getVersion()');
948
949
        if (method_exists($this, 'before_getVersion_callback')) {
950
            $this->{'before_getVersion_callback'}();
951
        }
952
953
        $config = ConfigProvider::getInstance();
954
955
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
956
        $provider->setRecord($this);
957
        $ver = $provider->getVersion();
958
959
        if (method_exists($this, 'after_getVersion_callback')) {
960
            $this->{'after_getVersion_callback'}();
961
        }
962
963
        self::$logger->debug('<<getVersion ['.$ver.']');
964
965
        return $ver;
966
    }
967
968
    /**
969
     * Builds a new database table for the Record class.
970
     *
971
     * @since 1.0
972
     *
973
     * @throws \Alpha\Exception\AlphaException
974
     */
975
    public function makeTable($checkIndexes = true)
976
    {
977
        self::$logger->debug('>>makeTable()');
978
979
        if (method_exists($this, 'before_makeTable_callback')) {
980
            $this->{'before_makeTable_callback'}();
981
        }
982
983
        $config = ConfigProvider::getInstance();
984
985
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
986
        $provider->setRecord($this);
987
        $provider->makeTable($checkIndexes);
988
989
        if (method_exists($this, 'after_makeTable_callback')) {
990
            $this->{'after_makeTable_callback'}();
991
        }
992
993
        self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
994
995
        self::$logger->debug('<<makeTable');
996
    }
997
998
    /**
999
     * Builds a new database table for the Record class to story it's history of changes.
1000
     *
1001
     * @since 1.2
1002
     *
1003
     * @throws \Alpha\Exception\AlphaException
1004
     */
1005
    public function makeHistoryTable()
1006
    {
1007
        self::$logger->debug('>>makeHistoryTable()');
1008
1009
        if (method_exists($this, 'before_makeHistoryTable_callback')) {
1010
            $this->{'before_makeHistoryTable_callback'}();
1011
        }
1012
1013
        $config = ConfigProvider::getInstance();
1014
1015
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1016
        $provider->setRecord($this);
1017
        $provider->makeHistoryTable();
1018
1019
        if (method_exists($this, 'after_makeHistoryTable_callback')) {
1020
            $this->{'after_makeHistoryTable_callback'}();
1021
        }
1022
1023
        self::$logger->info('Successfully created the table ['.$this->getTableName().'_history] for the class ['.get_class($this).']');
1024
1025
        self::$logger->debug('<<makeHistoryTable');
1026
    }
1027
1028
    /**
1029
     * Re-builds the table if the model requirements have changed.  All data is lost!
1030
     *
1031
     * @since 1.0
1032
     *
1033
     * @throws \Alpha\Exception\AlphaException
1034
     */
1035
    public function rebuildTable()
1036
    {
1037
        self::$logger->debug('>>rebuildTable()');
1038
1039
        if (method_exists($this, 'before_rebuildTable_callback')) {
1040
            $this->{'before_rebuildTable_callback'}();
1041
        }
1042
1043
        $config = ConfigProvider::getInstance();
1044
1045
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1046
        $provider->setRecord($this);
1047
        $provider->rebuildTable();
1048
1049
        if (method_exists($this, 'after_rebuildTable_callback')) {
1050
            $this->{'after_rebuildTable_callback'}();
1051
        }
1052
1053
        self::$logger->debug('<<rebuildTable');
1054
    }
1055
1056
    /**
1057
     * Drops the table if the model requirements have changed.  All data is lost!
1058
     *
1059
     * @since 1.0
1060
     *
1061
     * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
1062
     *
1063
     * @throws \Alpha\Exception\AlphaException
1064
     */
1065
    public function dropTable($tableName = null)
1066
    {
1067
        self::$logger->debug('>>dropTable()');
1068
1069
        if (method_exists($this, 'before_dropTable_callback')) {
1070
            $this->{'before_dropTable_callback'}();
1071
        }
1072
1073
        $config = ConfigProvider::getInstance();
1074
1075
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1076
        $provider->setRecord($this);
1077
        $provider->dropTable($tableName);
1078
1079
        if (method_exists($this, 'after_dropTable_callback')) {
1080
            $this->{'after_dropTable_callback'}();
1081
        }
1082
1083
        self::$logger->debug('<<dropTable');
1084
    }
1085
1086
    /**
1087
     * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
1088
     * database).
1089
     *
1090
     * @param string $propName The name of the new field to add to the database table.
1091
     *
1092
     * @since 1.0
1093
     *
1094
     * @throws \Alpha\Exception\AlphaException
1095
     */
1096
    public function addProperty($propName)
1097
    {
1098
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1099
1100
        $config = ConfigProvider::getInstance();
1101
1102
        if (method_exists($this, 'before_addProperty_callback')) {
1103
            $this->{'before_addProperty_callback'}();
1104
        }
1105
1106
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1107
        $provider->setRecord($this);
1108
        $provider->addProperty($propName);
1109
1110
        if (method_exists($this, 'after_addProperty_callback')) {
1111
            $this->{'after_addProperty_callback'}();
1112
        }
1113
1114
        self::$logger->debug('<<addProperty');
1115
    }
1116
1117
    /**
1118
     * Populates the current business object from the provided hash array.
1119
     *
1120
     * @param array $hashArray
1121
     *
1122
     * @since 1.2.1
1123
     */
1124
    public function populateFromArray($hashArray)
1125
    {
1126
        self::$logger->debug('>>populateFromArray(hashArray=['.print_r($hashArray, true).'])');
1127
1128
        // get the class attributes
1129
        $reflection = new ReflectionClass(get_class($this));
1130
        $properties = $reflection->getProperties();
1131
1132
        foreach ($properties as $propObj) {
1133
            $propName = $propObj->name;
1134
1135
            if (isset($hashArray[$propName])) {
1136
                if ($this->getPropObject($propName) instanceof Date || $this->getPropObject($propName) instanceof Timestamp) {
1137
                    $this->getPropObject($propName)->populateFromString($hashArray[$propName]);
1138
                } elseif ($this->getPropObject($propName) instanceof TypeInterface) {
1139
                    $this->getPropObject($propName)->setValue($hashArray[$propName]);
1140
                }
1141
1142
                if ($propName == 'version_num' && isset($hashArray['version_num'])) {
1143
                    $this->version_num->setValue($hashArray['version_num']);
1144
                }
1145
1146
                if ($this->getPropObject($propName) instanceof Relation) {
1147
                    $rel = $this->getPropObject($propName);
1148
1149
                    if ($rel->getRelationType() == 'MANY-TO-MANY') {
1150
                        $IDs = explode(',', $hashArray[$propName]);
1151
                        $rel->setRelatedIDs($IDs);
1152
                        $this->$propName = $rel;
1153
                    }
1154
                }
1155
            }
1156
        }
1157
1158
        self::$logger->debug('<<populateFromArray');
1159
    }
1160
1161
    /**
1162
     * Gets the maximum ID value from the database for this class type.
1163
     *
1164
     * @return int The maximum ID value in the class table.
1165
     *
1166
     * @since 1.0
1167
     *
1168
     * @throws \Alpha\Exception\AlphaException
1169
     */
1170
    public function getMAX()
1171
    {
1172
        self::$logger->debug('>>getMAX()');
1173
1174
        if (method_exists($this, 'before_getMAX_callback')) {
1175
            $this->{'before_getMAX_callback'}();
1176
        }
1177
1178
        $config = ConfigProvider::getInstance();
1179
1180
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1181
        $provider->setRecord($this);
1182
        $max = $provider->getMAX();
1183
1184
        if (method_exists($this, 'after_getMAX_callback')) {
1185
            $this->{'after_getMAX_callback'}();
1186
        }
1187
1188
        self::$logger->debug('<<getMAX ['.$max.']');
1189
1190
        return $max;
1191
    }
1192
1193
    /**
1194
     * Gets the count from the database for the amount of objects of this class.
1195
     *
1196
     * @param array $attributes The attributes to count the objects by (optional).
1197
     * @param array $values     The values of the attributes to count the objects by (optional).
1198
     *
1199
     * @return int
1200
     *
1201
     * @since 1.0
1202
     *
1203
     * @throws \Alpha\Exception\AlphaException
1204
     * @throws \Alpha\Exception\IllegalArguementException
1205
     */
1206
    public function getCount($attributes = array(), $values = array())
1207
    {
1208
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1209
1210
        if (method_exists($this, 'before_getCount_callback')) {
1211
            $this->{'before_getCount_callback'}();
1212
        }
1213
1214
        $config = ConfigProvider::getInstance();
1215
1216
        if (!is_array($attributes) || !is_array($values)) {
1217
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
1218
        }
1219
1220
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1221
        $provider->setRecord($this);
1222
        $count = $provider->getCount($attributes, $values);
1223
1224
        if (method_exists($this, 'after_getCount_callback')) {
1225
            $this->{'after_getCount_callback'}();
1226
        }
1227
1228
        self::$logger->debug('<<getCount ['.$count.']');
1229
1230
        return $count;
1231
    }
1232
1233
    /**
1234
     * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
1235
     * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
1236
     *
1237
     * @return int
1238
     *
1239
     * @since 1.2
1240
     *
1241
     * @throws \Alpha\Exception\AlphaException
1242
     */
1243
    public function getHistoryCount()
1244
    {
1245
        self::$logger->debug('>>getHistoryCount()');
1246
1247
        if (method_exists($this, 'before_getHistoryCount_callback')) {
1248
            $this->{'before_getHistoryCount_callback'}();
1249
        }
1250
1251
        $config = ConfigProvider::getInstance();
1252
1253
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1254
        $provider->setRecord($this);
1255
        $count = $provider->getHistoryCount();
1256
1257
        if (method_exists($this, 'after_getHistoryCount_callback')) {
1258
            $this->{'after_getHistoryCount_callback'}();
1259
        }
1260
1261
        self::$logger->debug('<<getHistoryCount ['.$count.']');
1262
1263
        return $count;
1264
    }
1265
1266
    /**
1267
     * Gets the ID for the object in zero-padded format (same as getID()).
1268
     *
1269
     * @return string 11 digit zero-padded ID value.
1270
     *
1271
     * @since 1.0
1272
     */
1273
    final public function getID()
1274
    {
1275
        if (self::$logger == null) {
1276
            self::$logger = new Logger('ActiveRecord');
1277
        }
1278
        self::$logger->debug('>>getID()');
1279
        $oid = str_pad($this->ID, 11, '0', STR_PAD_LEFT);
1280
        self::$logger->debug('<<getID ['.$oid.']');
1281
1282
        return $oid;
1283
    }
1284
1285
    /**
1286
     * Method for getting version number of the object.
1287
     *
1288
     * @return \Alpha\Model\Type\Integer The object version number.
1289
     *
1290
     * @since 1.0
1291
     */
1292
    public function getVersionNumber()
1293
    {
1294
        self::$logger->debug('>>getVersionNumber()');
1295
        self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
1296
1297
        return $this->version_num;
1298
    }
1299
1300
    /**
1301
     * Populate all of the enum options for this object from the database.
1302
     *
1303
     * @since 1.0
1304
     *
1305
     * @throws \Alpha\Exception\AlphaException
1306
     */
1307
    protected function setEnumOptions()
1308
    {
1309
        self::$logger->debug('>>setEnumOptions()');
1310
1311
        if (method_exists($this, 'before_setEnumOptions_callback')) {
1312
            $this->{'before_setEnumOptions_callback'}();
1313
        }
1314
1315
        $config = ConfigProvider::getInstance();
1316
1317
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1318
        $provider->setRecord($this);
1319
        try {
1320
            $provider->setEnumOptions();
1321
        } catch (NotImplementedException $e) {
1322
            self::$logger->debug($e->getMessage());
1323
        }
1324
1325
        self::$logger->debug('<<setEnumOptions');
1326
    }
1327
1328
    /**
1329
     * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
1330
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
1331
     * get.ucfirst($prop) method even if it exists, false otherwise (default).
1332
     *
1333
     * @param string $prop           The name of the object property to get.
1334
     * @param bool   $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
1335
     *
1336
     * @return mixed The property value.
1337
     *
1338
     * @since 1.0
1339
     *
1340
     * @throws \Alpha\Exception\IllegalArguementException
1341
     * @throws \Alpha\Exception\AlphaException
1342
     */
1343
    public function get($prop, $noChildMethods = false)
1344
    {
1345
        if (self::$logger == null) {
1346
            self::$logger = new Logger('ActiveRecord');
1347
        }
1348
1349
        self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
1350
1351
        if (method_exists($this, 'before_get_callback')) {
1352
            $this->{'before_get_callback'}();
1353
        }
1354
1355
        if (empty($prop)) {
1356
            throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
1357
        }
1358
1359
        // handle attributes with a get.ucfirst($prop) method
1360
        if (!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
1361
            if (method_exists($this, 'after_get_callback')) {
1362
                $this->{'after_get_callback'}();
1363
            }
1364
1365
            $methodName = 'get'.ucfirst($prop);
1366
1367
            self::$logger->debug('<<get ['.print_r($this->$methodName(), true).'])');
1368
1369
            return $this->$methodName();
1370
        } else {
1371
            // handle attributes with no dedicated child get.ucfirst($prop) method
1372
            if (isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
1373
                if (method_exists($this, 'after_get_callback')) {
1374
                    $this->{'after_get_callback'}();
1375
                }
1376
1377
                // complex types will have a getValue() method, return the value of that
1378
                self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
1379
1380
                return $this->$prop->getValue();
1381
            } elseif (isset($this->$prop)) {
1382
                if (method_exists($this, 'after_get_callback')) {
1383
                    $this->{'after_get_callback'}();
1384
                }
1385
1386
                // simple types returned as-is
1387
                self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
1388
1389
                return $this->$prop;
1390
            } else {
1391
                self::$logger->debug('<<get');
1392
                throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
1393
            }
1394
        }
1395
    }
1396
1397
    /**
1398
     * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
1399
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
1400
     * any get.ucfirst($prop) method even if it exists, false otherwise (default).
1401
     *
1402
     * @param string $prop           The name of the property to set.
1403
     * @param mixed  $value          The value of the property to set.
1404
     * @param bool   $noChildMethods Set to true if you do not want to use setters in the child object, defaults to false.
1405
     *
1406
     * @since 1.0
1407
     *
1408
     * @throws \Alpha\Exception\AlphaException
1409
     */
1410
    public function set($prop, $value, $noChildMethods = false)
1411
    {
1412
        self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
1413
1414
        if (method_exists($this, 'before_set_callback')) {
1415
            $this->{'before_set_callback'}();
1416
        }
1417
1418
        // handle attributes with a set.ucfirst($prop) method
1419
        if (!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
1420
            if (method_exists($this, 'after_set_callback')) {
1421
                $this->{'after_set_callback'}();
1422
            }
1423
1424
            $methodName = 'set'.ucfirst($prop);
1425
1426
            $this->$methodName($value);
1427
        } else {
1428
            // handle attributes with no dedicated child set.ucfirst($prop) method
1429
            if (isset($this->$prop)) {
1430
                if (method_exists($this, 'after_set_callback')) {
1431
                    $this->{'after_set_callback'}();
1432
                }
1433
1434
                // complex types will have a setValue() method to call
1435
                if (is_object($this->$prop) && get_class($this->$prop) !== false) {
1436
                    if (mb_strtoupper(get_class($this->$prop)) != 'DATE' && mb_strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
1437
                        $this->$prop->setValue($value);
1438
                    } else {
1439
                        // Date and Timestamp objects have a special setter accepting a string
1440
                        $this->$prop->populateFromString($value);
1441
                    }
1442
                } else {
1443
                    // simple types set directly
1444
                    $this->$prop = $value;
1445
                }
1446
            } else {
1447
                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.');
1448
            }
1449
        }
1450
        self::$logger->debug('<<set');
1451
    }
1452
1453
    /**
1454
     * Gets the property object rather than the value for complex attributes.  Returns false if
1455
     * the property exists but is private.
1456
     *
1457
     * @param string $prop The name of the property we are getting.
1458
     *
1459
     * @return \Alpha\Model\Type\Type|bool The complex type object found.
1460
     *
1461
     * @since 1.0
1462
     *
1463
     * @throws \Alpha\Exception\IllegalArguementException
1464
     */
1465
    public function getPropObject($prop)
1466
    {
1467
        self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
1468
1469
        if (method_exists($this, 'before_getPropObject_callback')) {
1470
            $this->{'before_getPropObject_callback'}();
1471
        }
1472
1473
        // get the class attributes
1474
        $reflection = new \ReflectionObject($this);
1475
        $properties = $reflection->getProperties();
1476
1477
        // firstly, check for private
1478
        $attribute = new ReflectionProperty($this, $prop);
1479
1480
        if ($attribute->isPrivate()) {
1481
            if (method_exists($this, 'after_getPropObject_callback')) {
1482
                $this->{'after_getPropObject_callback'}();
1483
            }
1484
1485
            self::$logger->debug('<<getPropObject [false]');
1486
1487
            return false;
1488
        }
1489
1490
        foreach ($properties as $propObj) {
1491
            $propName = $propObj->name;
1492
1493
            if ($prop == $propName) {
1494
                if (method_exists($this, 'after_getPropObject_callback')) {
1495
                    $this->{'after_getPropObject_callback'}();
1496
                }
1497
1498
                self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
1499
1500
                return $this->$prop;
1501
            }
1502
        }
1503
1504
        self::$logger->debug('<<getPropObject');
1505
        throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
1506
    }
1507
1508
    /**
1509
     * Checks to see if the table exists in the database for the current business class.
1510
     *
1511
     * @param bool $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1512
     *
1513
     * @return bool
1514
     *
1515
     * @since 1.0
1516
     *
1517
     * @throws \Alpha\Exception\AlphaException
1518
     */
1519
    public function checkTableExists($checkHistoryTable = false)
1520
    {
1521
        self::$logger->debug('>>checkTableExists()');
1522
1523
        if (method_exists($this, 'before_checkTableExists_callback')) {
1524
            $this->{'before_checkTableExists_callback'}();
1525
        }
1526
1527
        $config = ConfigProvider::getInstance();
1528
1529
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1530
        $provider->setRecord($this);
1531
        $tableExists = $provider->checkTableExists($checkHistoryTable);
1532
1533
        if (method_exists($this, 'after_checkTableExists_callback')) {
1534
            $this->{'after_checkTableExists_callback'}();
1535
        }
1536
1537
        self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1538
1539
        return $tableExists;
1540
    }
1541
1542
    /**
1543
     * Static method to check the database and see if the table for the indicated Record class name
1544
     * exists (assumes table name will be $recordClassName less "Object").
1545
     *
1546
     * @param string $recordClassName       The name of the business object class we are checking.
1547
     * @param bool   $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1548
     *
1549
     * @return bool
1550
     *
1551
     * @since 1.0
1552
     *
1553
     * @throws \Alpha\Exception\AlphaException
1554
     */
1555
    public static function checkRecordTableExists($recordClassName, $checkHistoryTable = false)
1556
    {
1557
        if (self::$logger == null) {
1558
            self::$logger = new Logger('ActiveRecord');
1559
        }
1560
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$recordClassName.'])');
1561
1562
        $config = ConfigProvider::getInstance();
1563
1564
        $provider = $config->get('db.provider.name');
1565
1566
        $tableExists = $provider::checkRecordTableExists($recordClassName, $checkHistoryTable);
1567
1568
        self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1569
1570
        return $tableExists;
1571
    }
1572
1573
    /**
1574
     * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
1575
     * database table is in sync with the class definition.
1576
     *
1577
     * @return bool
1578
     *
1579
     * @since 1.0
1580
     *
1581
     * @throws \Alpha\Exception\AlphaException
1582
     */
1583
    public function checkTableNeedsUpdate()
1584
    {
1585
        self::$logger->debug('>>checkTableNeedsUpdate()');
1586
1587
        $config = ConfigProvider::getInstance();
1588
1589
        if (method_exists($this, 'before_checkTableNeedsUpdate_callback')) {
1590
            $this->{'before_checkTableNeedsUpdate_callback'}();
1591
        }
1592
1593
        $tableExists = $this->checkTableExists();
1594
1595
        if (!$tableExists) {
1596
            self::$logger->debug('<<checkTableNeedsUpdate [true]');
1597
1598
            return true;
1599
        } else {
1600
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1601
            $provider->setRecord($this);
1602
            $updateRequired = $provider->checkTableNeedsUpdate();
1603
1604
            if (method_exists($this, 'after_checkTableNeedsUpdate_callback')) {
1605
                $this->{'after_checkTableNeedsUpdate_callback'}();
1606
            }
1607
1608
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1609
1610
            return $updateRequired;
1611
        }
1612
    }
1613
1614
    /**
1615
     * Returns an array containing any properties on the class which have not been created on the database
1616
     * table yet.
1617
     *
1618
     * @return array An array of missing fields in the database table.
1619
     *
1620
     * @since 1.0
1621
     *
1622
     * @throws \Alpha\Exception\AlphaException
1623
     */
1624
    public function findMissingFields()
1625
    {
1626
        self::$logger->debug('>>findMissingFields()');
1627
1628
        $config = ConfigProvider::getInstance();
1629
1630
        if (method_exists($this, 'before_findMissingFields_callback')) {
1631
            $this->{'before_findMissingFields_callback'}();
1632
        }
1633
1634
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1635
        $provider->setRecord($this);
1636
        $missingFields = $provider->findMissingFields();
1637
1638
        if (method_exists($this, 'after_findMissingFields_callback')) {
1639
            $this->{'after_findMissingFields_callback'}();
1640
        }
1641
1642
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1643
1644
        return $missingFields;
1645
    }
1646
1647
    /**
1648
     * Getter for the TABLE_NAME, which should be set by a child of this class.
1649
     *
1650
     * @return string The table name in the database.
1651
     *
1652
     * @since 1.0
1653
     *
1654
     * @throws \Alpha\Exception\AlphaException
1655
     */
1656
    public function getTableName()
1657
    {
1658
        self::$logger->debug('>>getTableName()');
1659
1660
        $className = get_class($this);
1661
1662
        $tableName = $className::TABLE_NAME;
1663
1664
        if (!empty($tableName)) {
1665
            self::$logger->debug('<<getTableName ['.$tableName.']');
1666
1667
            return $tableName;
1668
        } else {
1669
            throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
1670
        }
1671
    }
1672
1673
    /**
1674
     * Method for getting the ID of the person who created this record.
1675
     *
1676
     * @return \Alpha\Model\Type\Integer The ID of the creator.
1677
     *
1678
     * @since 1.0
1679
     */
1680
    public function getCreatorId()
1681
    {
1682
        self::$logger->debug('>>getCreatorId()');
1683
        self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
1684
1685
        return $this->created_by;
1686
    }
1687
1688
    /**
1689
     * Method for getting the ID of the person who updated this record.
1690
     *
1691
     * @return \Alpha\Model\Type\Integer The ID of the updator.
1692
     *
1693
     * @since 1.0
1694
     */
1695
    public function getUpdatorId()
1696
    {
1697
        self::$logger->debug('>>getUpdatorId()');
1698
        self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
1699
1700
        return $this->updated_by;
1701
    }
1702
1703
    /**
1704
     * Method for getting the date/time of when the Record was created.
1705
     *
1706
     * @return \Alpha\Model\Type\Timestamp
1707
     *
1708
     * @since 1.0
1709
     */
1710
    public function getCreateTS()
1711
    {
1712
        self::$logger->debug('>>getCreateTS()');
1713
        self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
1714
1715
        return $this->created_ts;
1716
    }
1717
1718
    /**
1719
     * Method for getting the date/time of when the Record was last updated.
1720
     *
1721
     * @return \Alpha\Model\Type\Timestamp
1722
     *
1723
     * @since 1.0
1724
     */
1725
    public function getUpdateTS()
1726
    {
1727
        self::$logger->debug('>>getUpdateTS()');
1728
        self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
1729
1730
        return $this->updated_ts;
1731
    }
1732
1733
    /**
1734
     * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this record.
1735
     *
1736
     * @param string $attributeName The name of the attribute to not save.
1737
     *
1738
     * @since 1.0
1739
     */
1740
    public function markTransient($attributeName)
1741
    {
1742
        self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
1743
        self::$logger->debug('<<markTransient');
1744
        array_push($this->transientAttributes, $attributeName);
1745
    }
1746
1747
    /**
1748
     * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this record,
1749
     * ensuring that it will be saved on the next attempt.
1750
     *
1751
     * @param string $attributeName The name of the attribute to save.
1752
     *
1753
     * @since 1.0
1754
     */
1755
    public function markPersistent($attributeName)
1756
    {
1757
        self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
1758
        self::$logger->debug('<<markPersistent');
1759
        $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
1760
    }
1761
1762
    /**
1763
     * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this record.
1764
     *
1765
     * @param string $attribute1Name The first attribute to mark unique in the database.
1766
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1767
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1768
     *
1769
     * @since 1.0
1770
     */
1771
    protected function markUnique($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1772
    {
1773
        self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1774
1775
        if (empty($attribute2Name)) {
1776
            array_push($this->uniqueAttributes, $attribute1Name);
1777
        } else {
1778
            // Process composite unique keys: add them seperated by a + sign
1779
            if ($attribute3Name == '') {
1780
                $attributes = $attribute1Name.'+'.$attribute2Name;
1781
            } else {
1782
                $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
1783
            }
1784
1785
            array_push($this->uniqueAttributes, $attributes);
1786
        }
1787
1788
        self::$logger->debug('<<markUnique');
1789
    }
1790
1791
    /**
1792
     * Returns the array of names of unique attributes on this record.
1793
     *
1794
     * @return array
1795
     *
1796
     * @since 1.1
1797
     */
1798
    public function getUniqueAttributes()
1799
    {
1800
        self::$logger->debug('>>getUniqueAttributes()');
1801
        self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
1802
1803
        return $this->uniqueAttributes;
1804
    }
1805
1806
    /**
1807
     * Gets an array of all of the names of the active database indexes for this class.
1808
     *
1809
     * @return array An array of database indexes on this table.
1810
     *
1811
     * @since 1.0
1812
     *
1813
     * @throws \Alpha\Exception\AlphaException
1814
     */
1815
    public function getIndexes()
1816
    {
1817
        self::$logger->debug('>>getIndexes()');
1818
1819
        $config = ConfigProvider::getInstance();
1820
1821
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1822
        $provider->setRecord($this);
1823
        $indexNames = $provider->getIndexes();
1824
1825
        self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
1826
1827
        return $indexNames;
1828
    }
1829
1830
    /**
1831
     * Creates a foreign key constraint (index) in the database on the given attribute.
1832
     *
1833
     * @param string $attributeName         The name of the attribute to apply the index on.
1834
     * @param string $relatedClass          The name of the related class in the format "NameObject".
1835
     * @param string $relatedClassAttribute The name of the field to relate to on the related class.
1836
     * @param string $indexName             The optional name for the index, will calculate if not provided.
1837
     *
1838
     * @since 1.0
1839
     *
1840
     * @throws \Alpha\Exception\FailedIndexCreateException
1841
     */
1842
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
1843
    {
1844
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
1845
1846
        $config = ConfigProvider::getInstance();
1847
1848
        if (method_exists($this, 'before_createForeignIndex_callback')) {
1849
            $this->{'before_createForeignIndex_callback'}();
1850
        }
1851
1852
        $relatedRecord = new $relatedClass();
1853
        $tableName = $relatedRecord->getTableName();
1854
1855
        // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
1856
        if ($this->getTableName() == $tableName) {
1857
            self::$logger->debug('<<createForeignIndex');
1858
1859
            return;
1860
        }
1861
1862
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1863
        $provider->setRecord($this);
1864
        $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName);
1865
1866
        if (method_exists($this, 'after_createForeignIndex_callback')) {
1867
            $this->{'after_createForeignIndex_callback'}();
1868
        }
1869
1870
        self::$logger->debug('<<createForeignIndex');
1871
    }
1872
1873
    /**
1874
     * Creates a unique index in the database on the given attribute(s).
1875
     *
1876
     * @param string $attribute1Name The first attribute to mark unique in the database.
1877
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1878
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1879
     *
1880
     * @since 1.0
1881
     *
1882
     * @throws \Alpha\Exception\FailedIndexCreateException
1883
     */
1884
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1885
    {
1886
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1887
1888
        if (method_exists($this, 'before_createUniqueIndex_callback')) {
1889
            $this->{'before_createUniqueIndex_callback'}();
1890
        }
1891
1892
        $config = ConfigProvider::getInstance();
1893
1894
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1895
        $provider->setRecord($this);
1896
        $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
1897
1898
        if (method_exists($this, 'after_createUniqueIndex_callback')) {
1899
            $this->{'before_createUniqueIndex_callback'}();
1900
        }
1901
1902
        self::$logger->debug('<<createUniqueIndex');
1903
    }
1904
1905
    /**
1906
     * Gets the data labels array.
1907
     *
1908
     * @return array An array of attribute labels.
1909
     *
1910
     * @since 1.0
1911
     */
1912
    public function getDataLabels()
1913
    {
1914
        self::$logger->debug('>>getDataLabels()');
1915
        self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
1916
1917
        return $this->dataLabels;
1918
    }
1919
1920
    /**
1921
     * Sets the data labels array.
1922
     *
1923
     * @param array $labels
1924
     *
1925
     * @throws \Alpha\Exception\IllegalArguementException
1926
     *
1927
     * @since 1.2
1928
     */
1929
    public function setDataLabels($labels)
1930
    {
1931
        self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
1932
1933
        if (is_array($labels)) {
1934
            $this->dataLabels = $labels;
1935
        } else {
1936
            throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
1937
        }
1938
1939
        self::$logger->debug('<<setDataLabels()');
1940
    }
1941
1942
    /**
1943
     * Gets the data label for the given attribute name.
1944
     *
1945
     * @param $att The attribute name to get the label for.
1946
     *
1947
     * @return string
1948
     *
1949
     * @since 1.0
1950
     *
1951
     * @throws \Alpha\Exception\IllegalArguementException
1952
     */
1953
    public function getDataLabel($att)
1954
    {
1955
        self::$logger->debug('>>getDataLabel(att=['.$att.'])');
1956
1957
        if (in_array($att, array_keys($this->dataLabels))) {
1958
            self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
1959
1960
            return $this->dataLabels[$att];
1961
        } else {
1962
            self::$logger->debug('<<getDataLabel');
1963
            throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
1964
        }
1965
    }
1966
1967
    /**
1968
     * Loops over the core and custom Record directories and builds an array of all of the Record class names in the system.
1969
     *
1970
     * @return array An array of business object class names.
1971
     *
1972
     * @since 1.0
1973
     */
1974
    public static function getRecordClassNames()
1975
    {
1976
        if (self::$logger == null) {
1977
            self::$logger = new Logger('ActiveRecord');
1978
        }
1979
        self::$logger->debug('>>getRecordClassNames()');
1980
1981
        $config = ConfigProvider::getInstance();
1982
1983
        $classNameArray = array();
1984
1985
        if (file_exists($config->get('app.root').'src/Model')) { // it is possible it has not been created yet...
1986
            // first get any custom records
1987
            $handle = opendir($config->get('app.root').'src/Model');
1988
1989
            // loop over the business object directory
1990
            while (false !== ($file = readdir($handle))) {
1991
                if (preg_match('/.php/', $file)) {
1992
                    $classname = 'Model\\'.mb_substr($file, 0, -4);
1993
1994
                    if (class_exists($classname)) {
1995
                        array_push($classNameArray, $classname);
1996
                    }
1997
                }
1998
            }
1999
        }
2000
2001
        // now loop over the core records provided with Alpha
2002
        if (file_exists($config->get('app.root').'Alpha/Model')) {
2003
            $handle = opendir($config->get('app.root').'Alpha/Model');
2004
        } else {
2005
            $handle = opendir($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model');
2006
        }
2007
2008
        // loop over the business object directory
2009
        while (false !== ($file = readdir($handle))) {
2010
            if (preg_match('/.php/', $file)) {
2011
                $classname = 'Alpha\\Model\\'.mb_substr($file, 0, -4);
2012
2013
                if (class_exists($classname) && substr($classname, 0, 24) != 'Alpha\\Model\\ActiveRecord') {
2014
                    array_push($classNameArray, $classname);
2015
                }
2016
            }
2017
        }
2018
2019
        asort($classNameArray);
2020
        self::$logger->debug('<<getRecordClassNames ['.var_export($classNameArray, true).']');
2021
2022
        return $classNameArray;
2023
    }
2024
2025
    /**
2026
     * Get the array of default attribute names.
2027
     *
2028
     * @return array An array of attribute names.
2029
     *
2030
     * @since 1.0
2031
     */
2032
    public function getDefaultAttributes()
2033
    {
2034
        self::$logger->debug('>>getDefaultAttributes()');
2035
        self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
2036
2037
        return $this->defaultAttributes;
2038
    }
2039
2040
    /**
2041
     * Get the array of transient attribute names.
2042
     *
2043
     * @return array An array of attribute names.
2044
     *
2045
     * @since 1.0
2046
     */
2047
    public function getTransientAttributes()
2048
    {
2049
        self::$logger->debug('>>getTransientAttributes()');
2050
        self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
2051
2052
        return $this->transientAttributes;
2053
    }
2054
2055
    /**
2056
     * Get the array of persistent attribute names, i.e. those that are saved in the database.
2057
     *
2058
     * @return array An array of attribute names.
2059
     *
2060
     * @since 1.0
2061
     */
2062
    public function getPersistentAttributes()
2063
    {
2064
        self::$logger->debug('>>getPersistentAttributes()');
2065
2066
        $attributes = array();
2067
2068
        // get the class attributes
2069
        $reflection = new ReflectionClass(get_class($this));
2070
        $properties = $reflection->getProperties();
2071
2072
        foreach ($properties as $propObj) {
2073
            $propName = $propObj->name;
2074
2075
            // filter transient attributes
2076
            if (!in_array($propName, $this->transientAttributes)) {
2077
                array_push($attributes, $propName);
2078
            }
2079
        }
2080
2081
        self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
2082
2083
        return $attributes;
2084
    }
2085
2086
    /**
2087
     * Setter for the Object ID (ID).
2088
     *
2089
     * @param int $ID The Object ID.
2090
     *
2091
     * @since 1.0
2092
     */
2093
    public function setID($ID)
2094
    {
2095
        self::$logger->debug('>>setID(ID=['.$ID.'])');
2096
        self::$logger->debug('<<setID');
2097
        $this->ID = $ID;
2098
    }
2099
2100
    /**
2101
     * Inspector to see if the business object is transient (not presently stored in the database).
2102
     *
2103
     * @return bool
2104
     *
2105
     * @since 1.0
2106
     */
2107
    public function isTransient()
2108
    {
2109
        self::$logger->debug('>>isTransient()');
2110
2111
        if (empty($this->ID) || !isset($this->ID) || $this->ID == '00000000000') {
2112
            self::$logger->debug('<<isTransient [true]');
2113
2114
            return true;
2115
        } else {
2116
            self::$logger->debug('<<isTransient [false]');
2117
2118
            return false;
2119
        }
2120
    }
2121
2122
    /**
2123
     * Get the last database query run on this object.
2124
     *
2125
     * @return string An SQL query string.
2126
     *
2127
     * @since 1.0
2128
     */
2129
    public function getLastQuery()
2130
    {
2131
        self::$logger->debug('>>getLastQuery()');
2132
        self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
2133
2134
        return $this->lastQuery;
2135
    }
2136
2137
    /**
2138
     * Unsets all of the attributes of this object to null.
2139
     *
2140
     * @since 1.0
2141
     */
2142
    private function clear()
2143
    {
2144
        self::$logger->debug('>>clear()');
2145
2146
        // get the class attributes
2147
        $reflection = new ReflectionClass(get_class($this));
2148
        $properties = $reflection->getProperties();
2149
2150
        foreach ($properties as $propObj) {
2151
            $propName = $propObj->name;
2152
            if (!$propObj->isPrivate()) {
2153
                unset($this->$propName);
2154
            }
2155
        }
2156
2157
        self::$logger->debug('<<clear');
2158
    }
2159
2160
    /**
2161
     * Reloads the object from the database, overwritting any attribute values in memory.
2162
     *
2163
     * @since 1.0
2164
     *
2165
     * @throws \Alpha\Exception\AlphaException
2166
     */
2167
    public function reload()
2168
    {
2169
        self::$logger->debug('>>reload()');
2170
2171
        if (!$this->isTransient()) {
2172
            $this->load($this->getID());
2173
        } else {
2174
            throw new AlphaException('Cannot reload transient object from database!');
2175
        }
2176
        self::$logger->debug('<<reload');
2177
    }
2178
2179
    /**
2180
     * Checks that a record exists for the Record in the database.
2181
     *
2182
     * @param int $ID The Object ID of the object we want to see whether it exists or not.
2183
     *
2184
     * @return bool
2185
     *
2186
     * @since 1.0
2187
     *
2188
     * @throws \Alpha\Exception\AlphaException
2189
     */
2190
    public function checkRecordExists($ID)
2191
    {
2192
        self::$logger->debug('>>checkRecordExists(ID=['.$ID.'])');
2193
2194
        if (method_exists($this, 'before_checkRecordExists_callback')) {
2195
            $this->{'before_checkRecordExists_callback'}();
2196
        }
2197
2198
        $config = ConfigProvider::getInstance();
2199
2200
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2201
        $provider->setRecord($this);
2202
        $recordExists = $provider->checkRecordExists($ID);
2203
2204
        if (method_exists($this, 'after_checkRecordExists_callback')) {
2205
            $this->{'after_checkRecordExists_callback'}();
2206
        }
2207
2208
        self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
2209
2210
        return $recordExists;
2211
    }
2212
2213
    /**
2214
     * Checks to see if the table name matches the classname, and if not if the table
2215
     * name matches the classname name of another record, i.e. the table is used to store
2216
     * multiple types of records.
2217
     *
2218
     * @return bool
2219
     *
2220
     * @since 1.0
2221
     *
2222
     * @throws \Alpha\Exception\BadTableNameException
2223
     */
2224
    public function isTableOverloaded()
2225
    {
2226
        self::$logger->debug('>>isTableOverloaded()');
2227
2228
        $config = ConfigProvider::getInstance();
2229
2230
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2231
        $provider->setRecord($this);
2232
        $isOverloaded = $provider->isTableOverloaded();
2233
2234
        self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
2235
2236
        return $isOverloaded;
2237
    }
2238
2239
    /**
2240
     * Starts a new database transaction.
2241
     *
2242
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2243
     *
2244
     * @since 1.0
2245
     *
2246
     * @throws \Alpha\Exception\AlphaException
2247
     */
2248
    public static function begin($record = null)
2249
    {
2250
        if (self::$logger == null) {
2251
            self::$logger = new Logger('ActiveRecord');
2252
        }
2253
        self::$logger->debug('>>begin()');
2254
2255
        $config = ConfigProvider::getInstance();
2256
2257
        if (isset($record)) {
2258
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2259
            $provider->setRecord($record);
2260
        } else {
2261
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2262
            $provider->setRecord(new Person());
2263
        }
2264
2265
        try {
2266
            $provider->begin();
2267
        } catch (\Exception $e) {
2268
            throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
2269
        }
2270
2271
        self::$logger->debug('<<begin');
2272
    }
2273
2274
    /**
2275
     * Commits the current database transaction.
2276
     *
2277
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2278
     *
2279
     * @since 1.0
2280
     *
2281
     * @throws \Alpha\Exception\FailedSaveException
2282
     */
2283
    public static function commit($record = null)
2284
    {
2285
        if (self::$logger == null) {
2286
            self::$logger = new Logger('ActiveRecord');
2287
        }
2288
        self::$logger->debug('>>commit()');
2289
2290
        $config = ConfigProvider::getInstance();
2291
2292
        if (isset($record)) {
2293
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2294
            $provider->setRecord($record);
2295
        } else {
2296
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2297
            $provider->setRecord(new Person());
2298
        }
2299
2300
        try {
2301
            $provider->commit();
2302
        } catch (\Exception $e) {
2303
            throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
2304
        }
2305
2306
        self::$logger->debug('<<commit');
2307
    }
2308
2309
    /**
2310
     * Aborts the current database transaction.
2311
     *
2312
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2313
     *
2314
     * @since 1.0
2315
     *
2316
     * @throws \Alpha\Exception\AlphaException
2317
     */
2318
    public static function rollback($record = null)
2319
    {
2320
        if (self::$logger == null) {
2321
            self::$logger = new Logger('ActiveRecord');
2322
        }
2323
        self::$logger->debug('>>rollback()');
2324
2325
        $config = ConfigProvider::getInstance();
2326
2327
        if (isset($record)) {
2328
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2329
            $provider->setRecord($record);
2330
        } else {
2331
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2332
            $provider->setRecord(new Person());
2333
        }
2334
2335
        try {
2336
            $provider->rollback();
2337
        } catch (\Exception $e) {
2338
            throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
2339
        }
2340
2341
        self::$logger->debug('<<rollback');
2342
    }
2343
2344
    /**
2345
     * Static method that tries to determine if the system database has been installed or not.
2346
     *
2347
     * @return bool
2348
     *
2349
     * @since 1.0
2350
     */
2351
    public static function isInstalled()
2352
    {
2353
        if (self::$logger == null) {
2354
            self::$logger = new Logger('ActiveRecord');
2355
        }
2356
        self::$logger->debug('>>isInstalled()');
2357
2358
        /*
2359
         * Install conditions are:
2360
         *
2361
         * 1. person table exists
2362
         * 2. rights table exists
2363
         */
2364
        if (self::checkRecordTableExists('Alpha\Model\Person') && self::checkRecordTableExists('Alpha\Model\Rights')) {
2365
            self::$logger->debug('<<isInstalled [true]');
2366
2367
            return true;
2368
        } else {
2369
            self::$logger->debug('<<isInstalled [false]');
2370
2371
            return false;
2372
        }
2373
    }
2374
2375
    /**
2376
     * Returns true if the Record has a Relation property called tags, false otherwise.
2377
     *
2378
     * @return bool
2379
     *
2380
     * @since 1.0
2381
     */
2382
    public function isTagged()
2383
    {
2384
        if (property_exists($this, 'taggedAttributes') && property_exists($this, 'tags') && $this->{'tags'} instanceof \Alpha\Model\Type\Relation) {
2385
            return true;
2386
        } else {
2387
            return false;
2388
        }
2389
    }
2390
2391
    /**
2392
     * Returns the contents of the taggedAttributes array, or an empty array if that does not exist.
2393
     *
2394
     * @return array
2395
     *
2396
     * @since 1.2.3
2397
     */
2398
    public function getTaggedAttributes()
2399
    {
2400
        if ($this->isTagged()) {
2401
            return $this->{'taggedAttributes'};
2402
        } else {
2403
            return array();
2404
        }
2405
    }
2406
2407
    /**
2408
     * Setter for the Record version number.
2409
     *
2410
     * @param int $versionNumber The version number.
2411
     *
2412
     * @since 1.0
2413
     */
2414
    private function setVersion($versionNumber)
2415
    {
2416
        $this->version_num->setValue($versionNumber);
2417
    }
2418
2419
    /**
2420
     * Cast a Record to another type of record.  A new Record will be returned with the same ID and
2421
     * version_num as the old record, so this is NOT a true cast but is a copy.  All attribute
2422
     * values will be copied accross.
2423
     *
2424
     * @param string                    $targetClassName     The fully-qualified name of the target Record class.
2425
     * @param \Alpha\Model\ActiveRecord $originalRecord      The original business object.
2426
     *
2427
     * @return \Alpha\Model\ActiveRecord The new business object resulting from the cast.
2428
     *
2429
     * @since 1.0
2430
     */
2431
    public function cast($targetClassName, $originalRecord)
2432
    {
2433
        $record = new $targetClassName();
2434
        $record->setID($originalRecord->getID());
2435
        $record->setVersion($originalRecord->getVersion());
2436
2437
        // get the class attributes
2438
        $originalRecordreflection = new ReflectionClass(get_class($originalRecord));
2439
        $originalRecordproperties = $originalRecordreflection->getProperties();
2440
        $newRecordreflection = new ReflectionClass($targetClassName);
2441
        $newRecordproperties = $newRecordreflection->getProperties();
2442
2443
        // copy the property values from the old Record to the new record
2444
2445
        if (count($originalRecordproperties) < count($newRecordproperties)) {
2446
            // the original Record is smaller, so loop over its properties
2447
            foreach ($originalRecordproperties as $propObj) {
2448
                $propName = $propObj->name;
2449
                if (!in_array($propName, $this->transientAttributes)) {
2450
                    $record->set($propName, $originalRecord->get($propName));
2451
                }
2452
            }
2453
        } else {
2454
            // the new Record is smaller, so loop over its properties
2455
            foreach ($newRecordproperties as $propObj) {
2456
                $propName = $propObj->name;
2457
                if (!in_array($propName, $this->transientAttributes)) {
2458
                    $record->set($propName, $originalRecord->get($propName));
2459
                }
2460
            }
2461
        }
2462
2463
        return $record;
2464
    }
2465
2466
    /**
2467
     * Returns the simple class name, stripped of the namespace.
2468
     *
2469
     * @return string
2470
     *
2471
     * @since 1.0
2472
     */
2473
    public function getFriendlyClassName()
2474
    {
2475
        $reflectClass = new ReflectionClass($this);
2476
2477
        return $reflectClass->getShortname();
2478
    }
2479
2480
    /**
2481
     * Check to see if an attribute exists on the record.
2482
     *
2483
     * @param string $attribute The attribute name.
2484
     *
2485
     * @return bool
2486
     *
2487
     * @since 1.0
2488
     */
2489
    public function hasAttribute($attribute)
2490
    {
2491
        return property_exists($this, $attribute);
2492
    }
2493
2494
    /**
2495
     * Stores the business object to the configured cache instance.
2496
     *
2497
     * @since 1.1
2498
     */
2499
    public function addToCache()
2500
    {
2501
        self::$logger->debug('>>addToCache()');
2502
        $config = ConfigProvider::getInstance();
2503
2504
        try {
2505
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2506
            $cache->set(get_class($this).'-'.$this->getID(), $this, 3600);
2507
        } catch (\Exception $e) {
2508
            self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').'] 
2509
                instance: ['.$e->getMessage().']');
2510
        }
2511
2512
        self::$logger->debug('<<addToCache');
2513
    }
2514
2515
    /**
2516
     * Removes the business object from the configured cache instance.
2517
     *
2518
     * @since 1.1
2519
     */
2520
    public function removeFromCache()
2521
    {
2522
        self::$logger->debug('>>removeFromCache()');
2523
        $config = ConfigProvider::getInstance();
2524
2525
        try {
2526
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2527
            $cache->delete(get_class($this).'-'.$this->getID());
2528
        } catch (\Exception $e) {
2529
            self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
2530
                instance: ['.$e->getMessage().']');
2531
        }
2532
2533
        self::$logger->debug('<<removeFromCache');
2534
    }
2535
2536
    /**
2537
     * Attempts to load the business object from the configured cache instance.
2538
     *
2539
     * @since 1.1
2540
     *
2541
     * @return bool
2542
     */
2543
    public function loadFromCache()
2544
    {
2545
        self::$logger->debug('>>loadFromCache()');
2546
        $config = ConfigProvider::getInstance();
2547
2548
        try {
2549
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2550
            $record = $cache->get(get_class($this).'-'.$this->getID());
2551
2552
            if (!$record) {
2553
                self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getID().']');
2554
                self::$logger->debug('<<loadFromCache: [false]');
2555
2556
                return false;
2557
            } else {
2558
                // get the class attributes
2559
                $reflection = new ReflectionClass(get_class($this));
2560
                $properties = $reflection->getProperties();
2561
2562
                foreach ($properties as $propObj) {
2563
                    $propName = $propObj->name;
2564
2565
                    // filter transient attributes
2566
                    if (!in_array($propName, $this->transientAttributes)) {
2567
                        $this->set($propName, $record->get($propName, true));
2568
                    } elseif (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
2569
                        $prop = $this->getPropObject($propName);
2570
2571
                        // handle the setting of ONE-TO-MANY relation values
2572
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
2573
                            $this->set($propObj->name, $this->getID());
2574
                        }
2575
                    }
2576
                }
2577
2578
                self::$logger->debug('<<loadFromCache: [true]');
2579
2580
                return true;
2581
            }
2582
        } catch (\Exception $e) {
2583
            self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
2584
             instance: ['.$e->getMessage().']');
2585
2586
            self::$logger->debug('<<loadFromCache: [false]');
2587
2588
            return false;
2589
        }
2590
    }
2591
2592
    /**
2593
     * Sets the last query executed on this business object.
2594
     *
2595
     * @param string $query
2596
     *
2597
     * @since 1.1
2598
     */
2599
    public function setLastQuery($query)
2600
    {
2601
        self::$logger->sql($query);
2602
        $this->lastQuery = $query;
2603
    }
2604
2605
    /**
2606
     * Re-initialize the static logger property on the Record after de-serialize, as PHP does
2607
     * not serialize static properties.
2608
     *
2609
     * @since 1.2
2610
     */
2611
    public function __wakeup()
2612
    {
2613
        if (self::$logger == null) {
2614
            self::$logger = new Logger(get_class($this));
2615
        }
2616
    }
2617
2618
    /**
2619
     * Sets maintainHistory attribute on this DAO.
2620
     *
2621
     * @param bool $maintainHistory
2622
     *
2623
     * @throws \Alpha\Exception\IllegalArguementException
2624
     *
2625
     * @since 1.2
2626
     */
2627
    public function setMaintainHistory($maintainHistory)
2628
    {
2629
        if (!is_bool($maintainHistory)) {
2630
            throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
2631
        }
2632
2633
        $this->maintainHistory = $maintainHistory;
2634
    }
2635
2636
    /**
2637
     * Gets the value of the  maintainHistory attribute.
2638
     *
2639
     * @return bool
2640
     *
2641
     * @since 1.2
2642
     */
2643
    public function getMaintainHistory()
2644
    {
2645
        return $this->maintainHistory;
2646
    }
2647
2648
    /**
2649
     * Return a hash array of the object containing attribute names and simplfied values.
2650
     *
2651
     * @return array
2652
     *
2653
     * @since  1.2.4
2654
     */
2655
    public function toArray()
2656
    {
2657
        // get the class attributes
2658
        $reflection = new ReflectionClass(get_class($this));
2659
        $properties = $reflection->getProperties();
2660
2661
        $propArray = array();
2662
2663
        foreach ($properties as $propObj) {
2664
            $propName = $propObj->name;
2665
2666
            if (!in_array($propName, $this->transientAttributes)) {
2667
                $val = $this->get($propName);
2668
2669
                if (is_object($val)) {
2670
                    $val = $val->getValue();
2671
                }
2672
2673
                $propArray[$propName] = $val;
2674
            }
2675
        }
2676
2677
        return $propArray;
2678
    }
2679
2680
    /**
2681
     * Check to see if the configured database exists.
2682
     *
2683
     * @return bool
2684
     *
2685
     * @since 2.0
2686
     */
2687
    public static function checkDatabaseExists()
2688
    {
2689
        $config = ConfigProvider::getInstance();
2690
2691
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2692
        $provider->setRecord(new Person());
2693
2694
        return $provider->checkDatabaseExists();
2695
    }
2696
2697
    /**
2698
     * Creates the configured database.
2699
     *
2700
     * @throws \Alpha\Exception\AlphaException
2701
     *
2702
     * @since 2.0
2703
     */
2704
    public static function createDatabase()
2705
    {
2706
        $config = ConfigProvider::getInstance();
2707
2708
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2709
        $provider->setRecord(new Person());
2710
        $provider->createDatabase();
2711
    }
2712
2713
    /**
2714
     * Drops the configured database.
2715
     *
2716
     * @throws \Alpha\Exception\AlphaException
2717
     *
2718
     * @since 2.0
2719
     */
2720
    public static function dropDatabase()
2721
    {
2722
        $config = ConfigProvider::getInstance();
2723
2724
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2725
        $provider->setRecord(new Person());
2726
        $provider->dropDatabase();
2727
    }
2728
2729
    /**
2730
     * Backup the configured database.
2731
     *
2732
     * @param string $targetFile The file that the backup data will be written to.
2733
     *
2734
     * @throws \Alpha\Exception\AlphaException
2735
     *
2736
     * @since 3.0
2737
     */
2738
    public static function backupDatabase($targetFile)
2739
    {
2740
        $config = ConfigProvider::getInstance();
2741
2742
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2743
        $provider->setRecord(new Person());
2744
        $provider->backupDatabase($targetFile);
2745
    }
2746
}
2747