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

ActiveRecord::loadClassDef()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.2728
c 0
b 0
f 0
cc 5
nc 8
nop 1
1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Date;
6
use Alpha\Model\Type\Integer;
7
use Alpha\Model\Type\Timestamp;
8
use Alpha\Model\Type\TypeInterface;
9
use Alpha\Model\Type\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