Completed
Push — develop ( 87a146...ca2570 )
by John
02:42 queued 10s
created

ActiveRecord::loadAllByAttribute()   A

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