ActiveRecord::makeHistoryTable()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
cc 3
nc 4
nop 0
1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Date;
6
use Alpha\Model\Type\Integer;
7
use Alpha\Model\Type\Timestamp;
8
use Alpha\Model\Type\TypeInterface;
9
use Alpha\Model\Type\Relation;
10
use Alpha\Model\Type\RelationLookup;
11
use Alpha\Util\Config\ConfigProvider;
12
use Alpha\Util\Logging\Logger;
13
use Alpha\Util\Service\ServiceFactory;
14
use Alpha\Exception\AlphaException;
15
use Alpha\Exception\FailedSaveException;
16
use Alpha\Exception\FailedDeleteException;
17
use Alpha\Exception\ValidationException;
18
use Alpha\Exception\RecordNotFoundException;
19
use Alpha\Exception\IllegalArguementException;
20
use Alpha\Exception\LockingException;
21
use Alpha\Exception\NotImplementedException;
22
use ReflectionClass;
23
use ReflectionProperty;
24
25
/**
26
 * Base active record class definition providing database storage via the configured provider.
27
 *
28
 * @since 1.0
29
 *
30
 * @author John Collins <[email protected]>
31
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
32
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
33
 * All rights reserved.
34
 *
35
 * <pre>
36
 * Redistribution and use in source and binary forms, with or
37
 * without modification, are permitted provided that the
38
 * following conditions are met:
39
 *
40
 * * Redistributions of source code must retain the above
41
 *   copyright notice, this list of conditions and the
42
 *   following disclaimer.
43
 * * Redistributions in binary form must reproduce the above
44
 *   copyright notice, this list of conditions and the
45
 *   following disclaimer in the documentation and/or other
46
 *   materials provided with the distribution.
47
 * * Neither the name of the Alpha Framework nor the names
48
 *   of its contributors may be used to endorse or promote
49
 *   products derived from this software without specific
50
 *   prior written permission.
51
 *
52
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
53
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
54
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
55
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
56
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
57
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
58
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
59
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
60
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
61
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
62
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
63
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65
 * </pre>
66
 */
67
abstract class ActiveRecord
68
{
69
    /**
70
     * The object ID.
71
     *
72
     * @var int
73
     *
74
     * @since 1.0
75
     */
76
    protected $ID;
77
78
    /**
79
     * The last database query run by this object.  Useful for tracing an error.
80
     *
81
     * @var string
82
     *
83
     * @since 1.0
84
     */
85
    protected $lastQuery;
86
87
    /**
88
     * The version number of the object, used for locking mechanism.
89
     *
90
     * @var \Alpha\Model\Type\Integer
91
     *
92
     * @since 1.0
93
     */
94
    protected $version_num;
95
96
    /**
97
     * The timestamp of creation.
98
     *
99
     * @var \Alpha\Model\Type\Timestamp
100
     *
101
     * @since 1.0
102
     */
103
    protected $created_ts;
104
105
    /**
106
     * The ID of the person who created this record.
107
     *
108
     * @var \Alpha\Model\Type\Integer
109
     *
110
     * @since 1.0
111
     */
112
    protected $created_by;
113
114
    /**
115
     * The timestamp of the last update.
116
     *
117
     * @var \Alpha\Model\Type\Timestamp
118
     *
119
     * @since 1.0
120
     */
121
    protected $updated_ts;
122
123
    /**
124
     * The ID of the person who last updated this record.
125
     *
126
     * @var \Alpha\Model\Type\Integer
127
     *
128
     * @since 1.0
129
     */
130
    protected $updated_by;
131
132
    /**
133
     * An array of the names of all of the default attributes of a persistent Record defined in this class.
134
     *
135
     * @var array
136
     *
137
     * @since 1.0
138
     */
139
    protected $defaultAttributes = array('ID', 'lastQuery', 'version_num', 'dataLabels', 'created_ts', 'created_by', 'updated_ts', 'updated_by', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
140
141
    /**
142
     * An array of the names of all of the transient attributes of a persistent Record which are not saved to the DB.
143
     *
144
     * @var array
145
     *
146
     * @since 1.0
147
     */
148
    protected $transientAttributes = array('lastQuery', 'dataLabels', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
149
150
    /**
151
     * An array of the uniquely-constained attributes of this persistent record.
152
     *
153
     * @var array
154
     *
155
     * @since 1.0
156
     */
157
    protected $uniqueAttributes = array();
158
159
    /**
160
     * An array of the data labels used for displaying class attributes.
161
     *
162
     * @var array
163
     *
164
     * @since 1.0
165
     */
166
    protected $dataLabels = array();
167
168
    /**
169
     * Trace logger.
170
     *
171
     * @var \Alpha\Util\Logging\Logger
172
     *
173
     * @since 1.0
174
     */
175
    private static $logger = null;
176
177
    /**
178
     * Determines if we will maintain a _history table for this record (default is false).
179
     *
180
     * @var bool
181
     *
182
     * @since 1.2
183
     */
184
    private $maintainHistory = false;
185
186
    /**
187
     * The constructor which sets up some housekeeping attributes.
188
     *
189
     * @since 1.0
190
     */
191
    public function __construct()
192
    {
193
        self::$logger = new Logger('ActiveRecord');
194
        self::$logger->debug('>>__construct()');
195
196
        $config = ConfigProvider::getInstance();
197
        $sessionProvider = $config->get('session.provider.name');
198
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
199
200
        set_exception_handler('Alpha\Util\ErrorHandlers::catchException');
201
        set_error_handler('Alpha\Util\ErrorHandlers::catchError', $config->get('php.error.log.level'));
202
203
        $this->version_num = new Integer(0);
204
        $this->created_ts = new Timestamp(date('Y-m-d H:i:s'));
205
        $person_ID = ($session->get('currentUser') != null ? $session->get('currentUser')->getID() : 0);
206
        $this->created_by = new Integer($person_ID);
207
        $this->updated_ts = new Timestamp(date('Y-m-d H:i:s'));
208
        $this->updated_by = new Integer($person_ID);
209
210
        self::$logger->debug('<<__construct');
211
    }
212
213
    /**
214
     * Disconnects the current database connection if one exists.
215
     *
216
     * @since 1.0
217
     */
218
    public static function disconnect()
219
    {
220
        $config = ConfigProvider::getInstance();
221
222
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
223
        $provider->disconnect();
224
    }
225
226
    /**
227
     * Returns a 2d array, where each element in the array is another array representing a database row.
228
     *
229
     * @param string $sqlQuery
230
     *
231
     * @return array
232
     *
233
     * @since 1.1
234
     *
235
     * @throws \Alpha\Exception\CustomQueryException
236
     */
237
    public function query($sqlQuery)
238
    {
239
        self::$logger->debug('>>query(sqlQuery=['.$sqlQuery.'])');
240
241
        $config = ConfigProvider::getInstance();
242
243
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
244
        $provider->setRecord($this);
245
        $result = $provider->query($sqlQuery);
246
247
        self::$logger->debug('<<query ['.print_r($result, true).']');
248
249
        return $result;
250
    }
251
252
    /**
253
     * Populates the child object with the properties retrived from the database for the object $ID.
254
     *
255
     * @param int $ID     The object ID of the business object to load.
256
     * @param int $version Optionaly, provide the version to load that version from the [tablename]_history table.
257
     *
258
     * @since 1.0
259
     *
260
     * @throws \Alpha\Exception\RecordNotFoundException
261
     */
262
    public function load($ID, $version = 0)
263
    {
264
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
265
266
        if (method_exists($this, 'before_load_callback')) {
267
            $this->{'before_load_callback'}();
268
        }
269
270
        $config = ConfigProvider::getInstance();
271
272
        $this->ID = $ID;
273
274
        if ($config->get('cache.provider.name') != '' && $this->loadFromCache()) {
275
            // Record was found in cache
276
        } else {
277
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
278
            $provider->setRecord($this);
279
            $provider->load($ID, $version);
280
281
            if ($config->get('cache.provider.name') != '') {
282
                $this->addToCache();
283
            }
284
        }
285
286
        $this->setEnumOptions();
287
288
        if (method_exists($this, 'after_load_callback')) {
289
            $this->{'after_load_callback'}();
290
        }
291
292
        self::$logger->debug('<<load');
293
    }
294
295
    /**
296
     * Load all old versions (if any) of this record from the [tablename]_history table.
297
     *
298
     * @param int $ID The object ID of the record to load.
299
     *
300
     * @return array An array containing objects of this type of record object, order by version.
301
     *
302
     * @since 2.0
303
     *
304
     * @throws \Alpha\Exception\RecordFoundException
305
     */
306
    public function loadAllOldVersions($ID)
307
    {
308
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
309
310
        $config = ConfigProvider::getInstance();
311
312
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
313
        $provider->setRecord($this);
314
        $objects = $provider->loadAllOldVersions($ID);
315
316
        self::$logger->debug('<<loadAllOldVersions['.count($objects).']');
317
318
        return $objects;
319
    }
320
321
    /**
322
     * Populates the child object from the database table by the given attribute value.
323
     *
324
     * @param string $attribute       The name of the attribute to load the object by.
325
     * @param string $value           The value of the attribute to load the object by.
326
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
327
     * @param array  $loadAttributes  The attributes to load from the database to this object (leave blank to load all attributes)
328
     *
329
     * @since 1.0
330
     *
331
     * @throws \Alpha\Exception\RecordNotFoundException
332
     */
333
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
334
    {
335
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
336
            loadAttributes=['.var_export($loadAttributes, true).'])');
337
338
        if (method_exists($this, 'before_loadByAttribute_callback')) {
339
            $this->{'before_loadByAttribute_callback'}();
340
        }
341
342
        $config = ConfigProvider::getInstance();
343
344
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
345
        $provider->setRecord($this);
346
        $provider->loadByAttribute($attribute, $value, $ignoreClassType, $loadAttributes);
347
348
        $this->setEnumOptions();
349
350
        if ($config->get('cache.provider.name') != '' && count($loadAttributes) == 0) { // we will only cache fully-populated records
351
            $this->addToCache();
352
        }
353
354
        if (method_exists($this, 'after_loadByAttribute_callback')) {
355
            $this->{'after_loadByAttribute_callback'}();
356
        }
357
358
        self::$logger->debug('<<loadByAttribute');
359
    }
360
361
    /**
362
     * Loads all of the objects of this class into an array which is returned.
363
     *
364
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
365
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
366
     * @param string $orderBy         The name of the field to sort the objects by.
367
     * @param string $order           The order to sort the objects by.
368
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
369
     *
370
     * @return array An array containing objects of this type of business object.
371
     *
372
     * @since 1.0
373
     *
374
     * @throws \Alpha\Exception\RecordNotFoundException
375
     */
376
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
377
    {
378
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
379
380
        if (method_exists($this, 'before_loadAll_callback')) {
381
            $this->{'before_loadAll_callback'}();
382
        }
383
384
        $config = ConfigProvider::getInstance();
385
386
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
387
        $provider->setRecord($this);
388
        $objects = $provider->loadAll($start, $limit, $orderBy, $order, $ignoreClassType);
389
390
        if (method_exists($this, 'after_loadAll_callback')) {
391
            $this->{'after_loadAll_callback'}();
392
        }
393
394
        self::$logger->debug('<<loadAll ['.count($objects).']');
395
396
        return $objects;
397
    }
398
399
    /**
400
     * Loads all of the objects of this class by the specified attribute into an array which is returned.
401
     *
402
     * @param string $attribute       The attribute to load the objects by.
403
     * @param string $value           The value of the attribute to load the objects by.
404
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
405
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
406
     * @param string $orderBy         The name of the field to sort the objects by.
407
     * @param string $order           The order to sort the objects by.
408
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
409
     * @param array  $constructorArgs An optional array of contructor arguements to pass to the records that will be generated and returned.  Supports a maximum of 5 arguements.
410
     *
411
     * @return array An array containing objects of this type of business object.
412
     *
413
     * @since 1.0
414
     *
415
     * @throws \Alpha\Exception\RecordNotFoundException
416
     * @throws \Alpha\Exception\IllegalArguementException
417
     */
418
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
419
    {
420
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
421
422
        if (method_exists($this, 'before_loadAllByAttribute_callback')) {
423
            $this->{'before_loadAllByAttribute_callback'}();
424
        }
425
426
        $config = ConfigProvider::getInstance();
427
428
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
429
        $provider->setRecord($this);
430
        $objects = $provider->loadAllByAttribute($attribute, $value, $start, $limit, $orderBy, $order, $ignoreClassType);
431
432
        if (method_exists($this, 'after_loadAllByAttribute_callback')) {
433
            $this->{'after_loadAllByAttribute_callback'}();
434
        }
435
436
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
437
438
        return $objects;
439
    }
440
441
    /**
442
     * Loads all of the objects of this class by the specified attributes into an array which is returned.
443
     *
444
     * @param array  $attributes      The attributes to load the objects by.
445
     * @param array  $values          The values of the attributes to load the objects by.
446
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
447
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
448
     * @param string $orderBy         The name of the field to sort the objects by.
449
     * @param string $order           The order to sort the objects by.
450
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
451
     *
452
     * @return array An array containing objects of this type of business object.
453
     *
454
     * @since 1.0
455
     *
456
     * @throws \Alpha\Exception\RecordNotFoundException
457
     * @throws \Alpha\Exception\IllegalArguementException
458
     */
459
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
460
    {
461
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
462
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
463
464
        if (method_exists($this, 'before_loadAllByAttributes_callback')) {
465
            $this->{'before_loadAllByAttributes_callback'}();
466
        }
467
468
        $config = ConfigProvider::getInstance();
469
470
        if (!is_array($attributes) || !is_array($values)) {
471
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).
472
                '] provided to loadAllByAttributes');
473
        }
474
475
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
476
        $provider->setRecord($this);
477
        $objects = $provider->loadAllByAttributes($attributes, $values, $start, $limit, $orderBy, $order, $ignoreClassType);
478
479
        if (method_exists($this, 'after_loadAllByAttributes_callback')) {
480
            $this->{'after_loadAllByAttributes_callback'}();
481
        }
482
483
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
484
485
        return $objects;
486
    }
487
488
    /**
489
     * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
490
     *
491
     * @param string $date            The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
492
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
493
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
494
     * @param string $orderBy         The name of the field to sort the objects by.
495
     * @param string $order           The order to sort the objects by.
496
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
497
     *
498
     * @return array An array containing objects of this type of business object.
499
     *
500
     * @since 1.0
501
     *
502
     * @throws \Alpha\Exception\RecordNotFoundException
503
     */
504
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
505
    {
506
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
507
508
        if (method_exists($this, 'before_loadAllByDayUpdated_callback')) {
509
            $this->{'before_loadAllByDayUpdated_callback'}();
510
        }
511
512
        $config = ConfigProvider::getInstance();
513
514
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
515
        $provider->setRecord($this);
516
        $objects = $provider->loadAllByDayUpdated($date, $start, $limit, $orderBy, $order, $ignoreClassType);
517
518
        if (method_exists($this, 'after_loadAllByDayUpdated_callback')) {
519
            $this->{'after_loadAllByDayUpdated_callback'}();
520
        }
521
522
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
523
524
        return $objects;
525
    }
526
527
    /**
528
     * Loads all of the specified attribute values of this class by the specified attribute into an
529
     * array which is returned.
530
     *
531
     * @param string $attribute       The attribute name to load the field values by.
532
     * @param string $value           The value of the attribute to load the field values by.
533
     * @param string $returnAttribute The name of the attribute to return.
534
     * @param string $order           The order to sort the records by.
535
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
536
     *
537
     * @return array An array of field values.
538
     *
539
     * @since 1.0
540
     *
541
     * @throws \Alpha\Exception\RecordNotFoundException
542
     */
543
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
544
    {
545
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
546
547
        $config = ConfigProvider::getInstance();
548
549
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
550
        $provider->setRecord($this);
551
        $values = $provider->loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order, $ignoreClassType);
552
553
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
554
555
        return $values;
556
    }
557
558
    /**
559
     * Saves the object.  If $this->ID is empty or null it will INSERT, otherwise UPDATE.
560
     *
561
     * @since 1.0
562
     *
563
     * @throws \Alpha\Exception\FailedSaveException
564
     * @throws \Alpha\Exception\LockingException
565
     * @throws \Alpha\Exception\ValidationException
566
     */
567
    public function save()
568
    {
569
        self::$logger->debug('>>save()');
570
571
        if (method_exists($this, 'before_save_callback')) {
572
            $this->{'before_save_callback'}();
573
        }
574
575
        $config = ConfigProvider::getInstance();
576
577
        // firstly we will validate the object before we try to save it
578
        $this->validate();
579
580
        $sessionProvider = $config->get('session.provider.name');
581
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
582
583
        if ($this->getVersion() != $this->getVersionNumber()->getValue()) {
584
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
585
        }
586
587
        // set the "updated by" fields, we can only set the user id if someone is logged in
588
        if ($session->get('currentUser') != null) {
589
            $this->set('updated_by', $session->get('currentUser')->getID());
590
        }
591
592
        $this->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
593
594
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
595
        $provider->setRecord($this);
596
        $provider->save();
597
598
        if ($config->get('cache.provider.name') != '') {
599
            $this->removeFromCache();
600
            $this->addToCache();
601
        }
602
603
        if (method_exists($this, 'after_save_callback')) {
604
            $this->{'after_save_callback'}();
605
        }
606
    }
607
608
    /**
609
     * Saves relationship values, including lookup entries, for this record.
610
     *
611
     * @since 3.0
612
     *
613
     * @throws \Alpha\Exception\FailedSaveException
614
     */
615
    public function saveRelations()
616
    {
617
        $reflection = new ReflectionClass(get_class($this));
618
        $properties = $reflection->getProperties();
619
620
        try {
621
            foreach ($properties as $propObj) {
622
                $propName = $propObj->name;
623
624
                if ($this->getPropObject($propName) instanceof Relation) {
625
                    $prop = $this->getPropObject($propName);
626
627
                    // handle the saving of MANY-TO-MANY relation values
628
                    if ($prop->getRelationType() == 'MANY-TO-MANY' && count($prop->getRelatedIDs()) > 0) {
629
                        try {
630
                            try {
631
                                // check to see if the rel is on this class
632
                                $side = $prop->getSide(get_class($this));
633
                            } catch (IllegalArguementException $iae) {
634
                                $side = $prop->getSide(get_parent_class($this));
635
                            }
636
637
                            $lookUp = $prop->getLookup();
638
639
                            // first delete all of the old RelationLookup objects for this rel
640
                            try {
641
                                if ($side == 'left') {
642
                                    $lookUp->deleteAllByAttribute('leftID', $this->getID());
643
                                } else {
644
                                    $lookUp->deleteAllByAttribute('rightID', $this->getID());
645
                                }
646
                            } catch (\Exception $e) {
647
                                throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
648
                            }
649
650
                            $IDs = $prop->getRelatedIDs();
651
652
                            if (isset($IDs) && !empty($IDs[0])) {
653
                                // now for each posted ID, create a new RelationLookup record and save
654
                                foreach ($IDs as $id) {
655
                                    $newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
656
                                    if ($side == 'left') {
657
                                        $newLookUp->set('leftID', $this->getID());
658
                                        $newLookUp->set('rightID', $id);
659
                                    } else {
660
                                        $newLookUp->set('rightID', $this->getID());
661
                                        $newLookUp->set('leftID', $id);
662
                                    }
663
                                    $newLookUp->save();
664
                                }
665
                            }
666
                        } catch (\Exception $e) {
667
                            throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
668
                        }
669
                    }
670
671
                    // handle the saving of ONE-TO-MANY relation values
672
                    if ($prop->getRelationType() == 'ONE-TO-MANY') {
673
                        $prop->setValue($this->getID());
674
                    }
675
                }
676
            }
677
        } catch (\Exception $e) {
678
            throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
679
        }
680
    }
681
682
    /**
683
     * Saves the field specified with the value supplied.  Only works for persistent records.  Note that no Alpha type
684
     * validation is performed with this method!
685
     *
686
     * @param string $attribute The name of the attribute to save.
687
     * @param mixed  $value     The value of the attribute to save.
688
     *
689
     * @since 1.0
690
     *
691
     * @throws \Alpha\Exception\IllegalArguementException
692
     * @throws \Alpha\Exception\FailedSaveException
693
     */
694
    public function saveAttribute($attribute, $value)
695
    {
696
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
697
698
        if (method_exists($this, 'before_saveAttribute_callback')) {
699
            $this->{'before_saveAttribute_callback'}();
700
        }
701
702
        $config = ConfigProvider::getInstance();
703
704
        if (!isset($this->$attribute)) {
705
            throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
706
        }
707
708
        if ($this->isTransient()) {
709
            throw new FailedSaveException('Cannot perform saveAttribute method on transient record!');
710
        }
711
712
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
713
        $provider->setRecord($this);
714
        $provider->saveAttribute($attribute, $value);
715
716
        if ($config->get('cache.provider.name') != '') {
717
            $this->removeFromCache();
718
            $this->addToCache();
719
        }
720
721
        if (method_exists($this, 'after_saveAttribute_callback')) {
722
            $this->{'after_saveAttribute_callback'}();
723
        }
724
725
        self::$logger->debug('<<saveAttribute');
726
    }
727
728
    /**
729
     * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
730
     *
731
     * @since 1.2
732
     *
733
     * @throws \Alpha\Exception\FailedSaveException
734
     */
735
    public function saveHistory()
736
    {
737
        self::$logger->debug('>>saveHistory()');
738
739
        if (method_exists($this, 'before_saveHistory_callback')) {
740
            $this->{'before_saveHistory_callback'}();
741
        }
742
743
        $config = ConfigProvider::getInstance();
744
745
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
746
        $provider->setRecord($this);
747
        $provider->saveHistory();
748
749
        if (method_exists($this, 'after_saveHistory_callback')) {
750
            $this->{'after_saveHistory_callback'}();
751
        }
752
    }
753
754
    /**
755
     * Validates the object to be saved.
756
     *
757
     * @since 1.0
758
     *
759
     * @throws \Alpha\Exception\ValidationException
760
     */
761
    protected function validate()
762
    {
763
        self::$logger->debug('>>validate()');
764
765
        if (method_exists($this, 'before_validate_callback')) {
766
            $this->{'before_validate_callback'}();
767
        }
768
769
        // get the class attributes
770
        $reflection = new ReflectionClass(get_class($this));
771
        $properties = $reflection->getProperties();
772
773
        foreach ($properties as $propObj) {
774
            $propName = $propObj->name;
775
            if (!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
776
                $propClass = new ReflectionClass($this->getPropObject($propName));
777
                $propClass = $propClass->getShortname();
778
                if (mb_strtoupper($propClass) != 'ENUM' &&
779
                mb_strtoupper($propClass) != 'DENUM' &&
780
                mb_strtoupper($propClass) != 'DENUMITEM' &&
781
                mb_strtoupper($propClass) != 'BOOLEAN') {
782
                    if ($this->getPropObject($propName) != false && !preg_match($this->getPropObject($propName)->getRule(), $this->getPropObject($propName)->getValue())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getRule() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Date, Alpha\Model\Type\Double, Alpha\Model\Type\HugeText, Alpha\Model\Type\Integer, Alpha\Model\Type\LargeText, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text, Alpha\Model\Type\Timestamp. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
783
                        self::$logger->debug('<<validate');
784
                        throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
785
                    }
786
                }
787
            }
788
        }
789
790
        if (method_exists($this, 'after_validate_callback')) {
791
            $this->{'after_validate_callback'}();
792
        }
793
794
        self::$logger->debug('<<validate');
795
    }
796
797
    /**
798
     * Deletes the current object from the database.
799
     *
800
     * @since 1.0
801
     *
802
     * @throws \Alpha\Exception\FailedDeleteException
803
     */
804
    public function delete()
805
    {
806
        self::$logger->debug('>>delete()');
807
808
        if (method_exists($this, 'before_delete_callback')) {
809
            $this->{'before_delete_callback'}();
810
        }
811
812
        $config = ConfigProvider::getInstance();
813
814
        // get the class attributes
815
        $reflection = new ReflectionClass(get_class($this));
816
        $properties = $reflection->getProperties();
817
818
        // check for any relations on this object, then remove them to prevent orphaned data
819
        foreach ($properties as $propObj) {
820
            $propName = $propObj->name;
821
822
            if (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
823
                $prop = $this->getPropObject($propName);
824
825
                // Handle MANY-TO-MANY rels
826
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
827
                    self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
828
829
                    try {
830
                        // check to see if the rel is on this class
831
                        $side = $prop->getSide(get_class($this));
832
                    } catch (IllegalArguementException $iae) {
833
                        $side = $prop->getSide(get_parent_class($this));
834
                    }
835
836
                    self::$logger->debug('Side is ['.$side.']'.$this->getID());
837
838
                    $lookUp = $prop->getLookup();
839
                    self::$logger->debug('Lookup object['.var_export($lookUp, true).']');
840
841
                    // delete all of the old RelationLookup objects for this rel
842
                    if ($side == 'left') {
843
                        $lookUp->deleteAllByAttribute('leftID', $this->getID());
844
                    } else {
845
                        $lookUp->deleteAllByAttribute('rightID', $this->getID());
846
                    }
847
                    self::$logger->debug('...done deleting!');
848
                }
849
850
                // should set related field values to null (MySQL is doing this for us as-is)
851
                if ($prop->getRelationType() == 'ONE-TO-MANY' && !$prop->getRelatedClass() == 'Alpha\Model\Tag') {
852
                    $relatedObjects = $prop->getRelated();
853
854
                    foreach ($relatedObjects as $object) {
855
                        $object->set($prop->getRelatedClassField(), null);
856
                        $object->save();
857
                    }
858
                }
859
860
                // in the case of tags, we will always remove the related tags once the Record is deleted
861
                if ($prop->getRelationType() == 'ONE-TO-MANY' && $prop->getRelatedClass() == 'Alpha\Model\Tag') {
862
                    // just making sure that the Relation is set to current ID as its transient
863
                    $prop->setValue($this->getID());
864
                    $relatedObjects = $prop->getRelated();
865
866
                    foreach ($relatedObjects as $object) {
867
                        $object->delete();
868
                    }
869
                }
870
            }
871
        }
872
873
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
874
        $provider->setRecord($this);
875
        $provider->delete();
876
877
        if ($config->get('cache.provider.name') != '') {
878
            $this->removeFromCache();
879
        }
880
881
        if (method_exists($this, 'after_delete_callback')) {
882
            $this->{'after_delete_callback'}();
883
        }
884
885
        $this->clear();
886
        self::$logger->debug('<<delete');
887
    }
888
889
    /**
890
     * Delete all object instances from the database by the specified attribute matching the value provided.
891
     *
892
     * @param string $attribute The name of the field to delete the objects by.
893
     * @param mixed  $value     The value of the field to delete the objects by.
894
     *
895
     * @return int The number of rows deleted.
896
     *
897
     * @since 1.0
898
     *
899
     * @throws \Alpha\Exception\FailedDeleteException
900
     */
901
    public function deleteAllByAttribute($attribute, $value)
902
    {
903
        self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
904
905
        if (method_exists($this, 'before_deleteAllByAttribute_callback')) {
906
            $this->{'before_deleteAllByAttribute_callback'}();
907
        }
908
909
        try {
910
            $doomedObjects = $this->loadAllByAttribute($attribute, $value);
911
            $deletedRowCount = 0;
912
913
            foreach ($doomedObjects as $object) {
914
                $object->delete();
915
                ++$deletedRowCount;
916
            }
917
        } catch (RecordNotFoundException $bonf) {
918
            // nothing found to delete
919
            self::$logger->warn($bonf->getMessage());
920
921
            return 0;
922
        } catch (AlphaException $e) {
923
            self::$logger->debug('<<deleteAllByAttribute [0]');
924
            throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
925
        }
926
927
        if (method_exists($this, 'after_deleteAllByAttribute_callback')) {
928
            $this->{'after_deleteAllByAttribute_callback'}();
929
        }
930
931
        self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
932
933
        return $deletedRowCount;
934
    }
935
936
    /**
937
     * Gets the version_num of the object from the database (returns 0 if the Record is not saved yet).
938
     *
939
     * @return int
940
     *
941
     * @since 1.0
942
     *
943
     * @throws \Alpha\Exception\RecordNotFoundException
944
     */
945
    public function getVersion()
946
    {
947
        self::$logger->debug('>>getVersion()');
948
949
        if (method_exists($this, 'before_getVersion_callback')) {
950
            $this->{'before_getVersion_callback'}();
951
        }
952
953
        $config = ConfigProvider::getInstance();
954
955
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
956
        $provider->setRecord($this);
957
        $ver = $provider->getVersion();
958
959
        if (method_exists($this, 'after_getVersion_callback')) {
960
            $this->{'after_getVersion_callback'}();
961
        }
962
963
        self::$logger->debug('<<getVersion ['.$ver.']');
964
965
        return $ver;
966
    }
967
968
    /**
969
     * Builds a new database table for the Record class.
970
     *
971
     * @since 1.0
972
     *
973
     * @throws \Alpha\Exception\AlphaException
974
     */
975
    public function makeTable($checkIndexes = true)
976
    {
977
        self::$logger->debug('>>makeTable()');
978
979
        if (method_exists($this, 'before_makeTable_callback')) {
980
            $this->{'before_makeTable_callback'}();
981
        }
982
983
        $config = ConfigProvider::getInstance();
984
985
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
986
        $provider->setRecord($this);
987
        $provider->makeTable($checkIndexes);
988
989
        if (method_exists($this, 'after_makeTable_callback')) {
990
            $this->{'after_makeTable_callback'}();
991
        }
992
993
        self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
994
995
        self::$logger->debug('<<makeTable');
996
    }
997
998
    /**
999
     * Builds a new database table for the Record class to story it's history of changes.
1000
     *
1001
     * @since 1.2
1002
     *
1003
     * @throws \Alpha\Exception\AlphaException
1004
     */
1005
    public function makeHistoryTable()
1006
    {
1007
        self::$logger->debug('>>makeHistoryTable()');
1008
1009
        if (method_exists($this, 'before_makeHistoryTable_callback')) {
1010
            $this->{'before_makeHistoryTable_callback'}();
1011
        }
1012
1013
        $config = ConfigProvider::getInstance();
1014
1015
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1016
        $provider->setRecord($this);
1017
        $provider->makeHistoryTable();
1018
1019
        if (method_exists($this, 'after_makeHistoryTable_callback')) {
1020
            $this->{'after_makeHistoryTable_callback'}();
1021
        }
1022
1023
        self::$logger->info('Successfully created the table ['.$this->getTableName().'_history] for the class ['.get_class($this).']');
1024
1025
        self::$logger->debug('<<makeHistoryTable');
1026
    }
1027
1028
    /**
1029
     * Re-builds the table if the model requirements have changed.  All data is lost!
1030
     *
1031
     * @since 1.0
1032
     *
1033
     * @throws \Alpha\Exception\AlphaException
1034
     */
1035
    public function rebuildTable()
1036
    {
1037
        self::$logger->debug('>>rebuildTable()');
1038
1039
        if (method_exists($this, 'before_rebuildTable_callback')) {
1040
            $this->{'before_rebuildTable_callback'}();
1041
        }
1042
1043
        $config = ConfigProvider::getInstance();
1044
1045
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1046
        $provider->setRecord($this);
1047
        $provider->rebuildTable();
1048
1049
        if (method_exists($this, 'after_rebuildTable_callback')) {
1050
            $this->{'after_rebuildTable_callback'}();
1051
        }
1052
1053
        self::$logger->debug('<<rebuildTable');
1054
    }
1055
1056
    /**
1057
     * Drops the table if the model requirements have changed.  All data is lost!
1058
     *
1059
     * @since 1.0
1060
     *
1061
     * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
1062
     *
1063
     * @throws \Alpha\Exception\AlphaException
1064
     */
1065
    public function dropTable($tableName = null)
1066
    {
1067
        self::$logger->debug('>>dropTable()');
1068
1069
        if (method_exists($this, 'before_dropTable_callback')) {
1070
            $this->{'before_dropTable_callback'}();
1071
        }
1072
1073
        $config = ConfigProvider::getInstance();
1074
1075
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1076
        $provider->setRecord($this);
1077
        $provider->dropTable($tableName);
1078
1079
        if (method_exists($this, 'after_dropTable_callback')) {
1080
            $this->{'after_dropTable_callback'}();
1081
        }
1082
1083
        self::$logger->debug('<<dropTable');
1084
    }
1085
1086
    /**
1087
     * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
1088
     * database).
1089
     *
1090
     * @param string $propName The name of the new field to add to the database table.
1091
     *
1092
     * @since 1.0
1093
     *
1094
     * @throws \Alpha\Exception\AlphaException
1095
     */
1096
    public function addProperty($propName)
1097
    {
1098
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1099
1100
        $config = ConfigProvider::getInstance();
1101
1102
        if (method_exists($this, 'before_addProperty_callback')) {
1103
            $this->{'before_addProperty_callback'}();
1104
        }
1105
1106
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1107
        $provider->setRecord($this);
1108
        $provider->addProperty($propName);
1109
1110
        if (method_exists($this, 'after_addProperty_callback')) {
1111
            $this->{'after_addProperty_callback'}();
1112
        }
1113
1114
        self::$logger->debug('<<addProperty');
1115
    }
1116
1117
    /**
1118
     * Populates the current business object from the provided hash array.
1119
     *
1120
     * @param array $hashArray
1121
     *
1122
     * @since 1.2.1
1123
     */
1124
    public function populateFromArray($hashArray)
1125
    {
1126
        self::$logger->debug('>>populateFromArray(hashArray=['.print_r($hashArray, true).'])');
1127
1128
        // get the class attributes
1129
        $reflection = new ReflectionClass(get_class($this));
1130
        $properties = $reflection->getProperties();
1131
1132
        foreach ($properties as $propObj) {
1133
            $propName = $propObj->name;
1134
1135
            if (isset($hashArray[$propName])) {
1136
                if ($this->getPropObject($propName) instanceof Date || $this->getPropObject($propName) instanceof Timestamp) {
1137
                    $this->getPropObject($propName)->populateFromString($hashArray[$propName]);
1138
                } elseif ($this->getPropObject($propName) instanceof TypeInterface) {
1139
                    $this->getPropObject($propName)->setValue($hashArray[$propName]);
1140
                }
1141
1142
                if ($propName == 'version_num' && isset($hashArray['version_num'])) {
1143
                    $this->version_num->setValue($hashArray['version_num']);
1144
                }
1145
1146
                if ($this->getPropObject($propName) instanceof Relation) {
1147
                    $rel = $this->getPropObject($propName);
1148
1149
                    if ($rel->getRelationType() == 'MANY-TO-MANY') {
1150
                        $IDs = explode(',', $hashArray[$propName]);
1151
                        $rel->setRelatedIDs($IDs);
1152
                        $this->$propName = $rel;
1153
                    }
1154
                }
1155
            }
1156
        }
1157
1158
        self::$logger->debug('<<populateFromArray');
1159
    }
1160
1161
    /**
1162
     * Gets the maximum ID value from the database for this class type.
1163
     *
1164
     * @return int The maximum ID value in the class table.
1165
     *
1166
     * @since 1.0
1167
     *
1168
     * @throws \Alpha\Exception\AlphaException
1169
     */
1170
    public function getMAX()
1171
    {
1172
        self::$logger->debug('>>getMAX()');
1173
1174
        if (method_exists($this, 'before_getMAX_callback')) {
1175
            $this->{'before_getMAX_callback'}();
1176
        }
1177
1178
        $config = ConfigProvider::getInstance();
1179
1180
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1181
        $provider->setRecord($this);
1182
        $max = $provider->getMAX();
1183
1184
        if (method_exists($this, 'after_getMAX_callback')) {
1185
            $this->{'after_getMAX_callback'}();
1186
        }
1187
1188
        self::$logger->debug('<<getMAX ['.$max.']');
1189
1190
        return $max;
1191
    }
1192
1193
    /**
1194
     * Gets the count from the database for the amount of objects of this class.
1195
     *
1196
     * @param array $attributes The attributes to count the objects by (optional).
1197
     * @param array $values     The values of the attributes to count the objects by (optional).
1198
     *
1199
     * @return int
1200
     *
1201
     * @since 1.0
1202
     *
1203
     * @throws \Alpha\Exception\AlphaException
1204
     * @throws \Alpha\Exception\IllegalArguementException
1205
     */
1206
    public function getCount($attributes = array(), $values = array())
1207
    {
1208
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1209
1210
        if (method_exists($this, 'before_getCount_callback')) {
1211
            $this->{'before_getCount_callback'}();
1212
        }
1213
1214
        $config = ConfigProvider::getInstance();
1215
1216
        if (!is_array($attributes) || !is_array($values)) {
1217
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
1218
        }
1219
1220
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1221
        $provider->setRecord($this);
1222
        $count = $provider->getCount($attributes, $values);
1223
1224
        if (method_exists($this, 'after_getCount_callback')) {
1225
            $this->{'after_getCount_callback'}();
1226
        }
1227
1228
        self::$logger->debug('<<getCount ['.$count.']');
1229
1230
        return $count;
1231
    }
1232
1233
    /**
1234
     * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
1235
     * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
1236
     *
1237
     * @return int
1238
     *
1239
     * @since 1.2
1240
     *
1241
     * @throws \Alpha\Exception\AlphaException
1242
     */
1243
    public function getHistoryCount()
1244
    {
1245
        self::$logger->debug('>>getHistoryCount()');
1246
1247
        if (method_exists($this, 'before_getHistoryCount_callback')) {
1248
            $this->{'before_getHistoryCount_callback'}();
1249
        }
1250
1251
        $config = ConfigProvider::getInstance();
1252
1253
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1254
        $provider->setRecord($this);
1255
        $count = $provider->getHistoryCount();
1256
1257
        if (method_exists($this, 'after_getHistoryCount_callback')) {
1258
            $this->{'after_getHistoryCount_callback'}();
1259
        }
1260
1261
        self::$logger->debug('<<getHistoryCount ['.$count.']');
1262
1263
        return $count;
1264
    }
1265
1266
    /**
1267
     * Gets the ID for the object in zero-padded format (same as getID()).
1268
     *
1269
     * @return string 11 digit zero-padded ID value.
1270
     *
1271
     * @since 1.0
1272
     */
1273
    final public function getID()
1274
    {
1275
        if (self::$logger == null) {
1276
            self::$logger = new Logger('ActiveRecord');
1277
        }
1278
        self::$logger->debug('>>getID()');
1279
        $oid = str_pad($this->ID, 11, '0', STR_PAD_LEFT);
1280
        self::$logger->debug('<<getID ['.$oid.']');
1281
1282
        return $oid;
1283
    }
1284
1285
    /**
1286
     * Method for getting version number of the object.
1287
     *
1288
     * @return \Alpha\Model\Type\Integer The object version number.
1289
     *
1290
     * @since 1.0
1291
     */
1292
    public function getVersionNumber()
1293
    {
1294
        self::$logger->debug('>>getVersionNumber()');
1295
        self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
1296
1297
        return $this->version_num;
1298
    }
1299
1300
    /**
1301
     * Populate all of the enum options for this object from the database.
1302
     *
1303
     * @since 1.0
1304
     *
1305
     * @throws \Alpha\Exception\AlphaException
1306
     */
1307
    protected function setEnumOptions()
1308
    {
1309
        self::$logger->debug('>>setEnumOptions()');
1310
1311
        if (method_exists($this, 'before_setEnumOptions_callback')) {
1312
            $this->{'before_setEnumOptions_callback'}();
1313
        }
1314
1315
        $config = ConfigProvider::getInstance();
1316
1317
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1318
        $provider->setRecord($this);
1319
        try {
1320
            $provider->setEnumOptions();
1321
        } catch (NotImplementedException $e) {
1322
            self::$logger->debug($e->getMessage());
1323
        }
1324
1325
        self::$logger->debug('<<setEnumOptions');
1326
    }
1327
1328
    /**
1329
     * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
1330
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
1331
     * get.ucfirst($prop) method even if it exists, false otherwise (default).
1332
     *
1333
     * @param string $prop           The name of the object property to get.
1334
     * @param bool   $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
1335
     *
1336
     * @return mixed The property value.
1337
     *
1338
     * @since 1.0
1339
     *
1340
     * @throws \Alpha\Exception\IllegalArguementException
1341
     * @throws \Alpha\Exception\AlphaException
1342
     */
1343
    public function get($prop, $noChildMethods = false)
1344
    {
1345
        if (self::$logger == null) {
1346
            self::$logger = new Logger('ActiveRecord');
1347
        }
1348
1349
        self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
1350
1351
        if (method_exists($this, 'before_get_callback')) {
1352
            $this->{'before_get_callback'}();
1353
        }
1354
1355
        if (empty($prop)) {
1356
            throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
1357
        }
1358
1359
        // handle attributes with a get.ucfirst($prop) method
1360
        if (!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
1361
            if (method_exists($this, 'after_get_callback')) {
1362
                $this->{'after_get_callback'}();
1363
            }
1364
1365
            $methodName = 'get'.ucfirst($prop);
1366
1367
            self::$logger->debug('<<get ['.print_r($this->$methodName(), true).'])');
1368
1369
            return $this->$methodName();
1370
        } else {
1371
            // handle attributes with no dedicated child get.ucfirst($prop) method
1372
            if (isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
1373
                if (method_exists($this, 'after_get_callback')) {
1374
                    $this->{'after_get_callback'}();
1375
                }
1376
1377
                // complex types will have a getValue() method, return the value of that
1378
                self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
1379
1380
                return $this->$prop->getValue();
1381
            } elseif (isset($this->$prop)) {
1382
                if (method_exists($this, 'after_get_callback')) {
1383
                    $this->{'after_get_callback'}();
1384
                }
1385
1386
                // simple types returned as-is
1387
                self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
1388
1389
                return $this->$prop;
1390
            } else {
1391
                self::$logger->debug('<<get');
1392
                throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
1393
            }
1394
        }
1395
    }
1396
1397
    /**
1398
     * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
1399
     * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
1400
     * any get.ucfirst($prop) method even if it exists, false otherwise (default).
1401
     *
1402
     * @param string $prop           The name of the property to set.
1403
     * @param mixed  $value          The value of the property to set.
1404
     * @param bool   $noChildMethods Set to true if you do not want to use setters in the child object, defaults to false.
1405
     *
1406
     * @since 1.0
1407
     *
1408
     * @throws \Alpha\Exception\AlphaException
1409
     */
1410
    public function set($prop, $value, $noChildMethods = false)
1411
    {
1412
        self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
1413
1414
        if (method_exists($this, 'before_set_callback')) {
1415
            $this->{'before_set_callback'}();
1416
        }
1417
1418
        // handle attributes with a set.ucfirst($prop) method
1419
        if (!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
1420
            if (method_exists($this, 'after_set_callback')) {
1421
                $this->{'after_set_callback'}();
1422
            }
1423
1424
            $methodName = 'set'.ucfirst($prop);
1425
1426
            $this->$methodName($value);
1427
        } else {
1428
            // handle attributes with no dedicated child set.ucfirst($prop) method
1429
            if (isset($this->$prop)) {
1430
                if (method_exists($this, 'after_set_callback')) {
1431
                    $this->{'after_set_callback'}();
1432
                }
1433
1434
                // complex types will have a setValue() method to call
1435
                if (is_object($this->$prop) && get_class($this->$prop) !== false) {
1436
                    if (mb_strtoupper(get_class($this->$prop)) != 'DATE' && mb_strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
1437
                        $this->$prop->setValue($value);
1438
                    } else {
1439
                        // Date and Timestamp objects have a special setter accepting a string
1440
                        $this->$prop->populateFromString($value);
1441
                    }
1442
                } else {
1443
                    // simple types set directly
1444
                    $this->$prop = $value;
1445
                }
1446
            } else {
1447
                throw new AlphaException('Could not set the property ['.$prop.'] on the object of the class ['.get_class($this).'].  Property may not exist, or else does not have a setValue() method and is private or protected.');
1448
            }
1449
        }
1450
        self::$logger->debug('<<set');
1451
    }
1452
1453
    /**
1454
     * Gets the property object rather than the value for complex attributes.  Returns false if
1455
     * the property exists but is private.
1456
     *
1457
     * @param string $prop The name of the property we are getting.
1458
     *
1459
     * @return \Alpha\Model\Type\Type|bool The complex type object found.
1460
     *
1461
     * @since 1.0
1462
     *
1463
     * @throws \Alpha\Exception\IllegalArguementException
1464
     */
1465
    public function getPropObject($prop)
1466
    {
1467
        self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
1468
1469
        if (method_exists($this, 'before_getPropObject_callback')) {
1470
            $this->{'before_getPropObject_callback'}();
1471
        }
1472
1473
        // get the class attributes
1474
        $reflection = new \ReflectionObject($this);
1475
        $properties = $reflection->getProperties();
1476
1477
        // firstly, check for private
1478
        $attribute = new ReflectionProperty($this, $prop);
1479
1480
        if ($attribute->isPrivate()) {
1481
            if (method_exists($this, 'after_getPropObject_callback')) {
1482
                $this->{'after_getPropObject_callback'}();
1483
            }
1484
1485
            self::$logger->debug('<<getPropObject [false]');
1486
1487
            return false;
1488
        }
1489
1490
        foreach ($properties as $propObj) {
1491
            $propName = $propObj->name;
1492
1493
            if ($prop == $propName) {
1494
                if (method_exists($this, 'after_getPropObject_callback')) {
1495
                    $this->{'after_getPropObject_callback'}();
1496
                }
1497
1498
                self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
1499
1500
                return $this->$prop;
1501
            }
1502
        }
1503
1504
        self::$logger->debug('<<getPropObject');
1505
        throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
1506
    }
1507
1508
    /**
1509
     * Checks to see if the table exists in the database for the current business class.
1510
     *
1511
     * @param bool $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1512
     *
1513
     * @return bool
1514
     *
1515
     * @since 1.0
1516
     *
1517
     * @throws \Alpha\Exception\AlphaException
1518
     */
1519
    public function checkTableExists($checkHistoryTable = false)
1520
    {
1521
        self::$logger->debug('>>checkTableExists()');
1522
1523
        if (method_exists($this, 'before_checkTableExists_callback')) {
1524
            $this->{'before_checkTableExists_callback'}();
1525
        }
1526
1527
        $config = ConfigProvider::getInstance();
1528
1529
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1530
        $provider->setRecord($this);
1531
        $tableExists = $provider->checkTableExists($checkHistoryTable);
1532
1533
        if (method_exists($this, 'after_checkTableExists_callback')) {
1534
            $this->{'after_checkTableExists_callback'}();
1535
        }
1536
1537
        self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1538
1539
        return $tableExists;
1540
    }
1541
1542
    /**
1543
     * Static method to check the database and see if the table for the indicated Record class name
1544
     * exists (assumes table name will be $recordClassName less "Object").
1545
     *
1546
     * @param string $recordClassName       The name of the business object class we are checking.
1547
     * @param bool   $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
1548
     *
1549
     * @return bool
1550
     *
1551
     * @since 1.0
1552
     *
1553
     * @throws \Alpha\Exception\AlphaException
1554
     */
1555
    public static function checkRecordTableExists($recordClassName, $checkHistoryTable = false)
1556
    {
1557
        if (self::$logger == null) {
1558
            self::$logger = new Logger('ActiveRecord');
1559
        }
1560
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$recordClassName.'])');
1561
1562
        $config = ConfigProvider::getInstance();
1563
1564
        $provider = $config->get('db.provider.name');
1565
1566
        $tableExists = $provider::checkRecordTableExists($recordClassName, $checkHistoryTable);
1567
1568
        self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1569
1570
        return $tableExists;
1571
    }
1572
1573
    /**
1574
     * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
1575
     * database table is in sync with the class definition.
1576
     *
1577
     * @return bool
1578
     *
1579
     * @since 1.0
1580
     *
1581
     * @throws \Alpha\Exception\AlphaException
1582
     */
1583
    public function checkTableNeedsUpdate()
1584
    {
1585
        self::$logger->debug('>>checkTableNeedsUpdate()');
1586
1587
        $config = ConfigProvider::getInstance();
1588
1589
        if (method_exists($this, 'before_checkTableNeedsUpdate_callback')) {
1590
            $this->{'before_checkTableNeedsUpdate_callback'}();
1591
        }
1592
1593
        $tableExists = $this->checkTableExists();
1594
1595
        if (!$tableExists) {
1596
            self::$logger->debug('<<checkTableNeedsUpdate [true]');
1597
1598
            return true;
1599
        } else {
1600
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1601
            $provider->setRecord($this);
1602
            $updateRequired = $provider->checkTableNeedsUpdate();
1603
1604
            if (method_exists($this, 'after_checkTableNeedsUpdate_callback')) {
1605
                $this->{'after_checkTableNeedsUpdate_callback'}();
1606
            }
1607
1608
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1609
1610
            return $updateRequired;
1611
        }
1612
    }
1613
1614
    /**
1615
     * Returns an array containing any properties on the class which have not been created on the database
1616
     * table yet.
1617
     *
1618
     * @return array An array of missing fields in the database table.
1619
     *
1620
     * @since 1.0
1621
     *
1622
     * @throws \Alpha\Exception\AlphaException
1623
     */
1624
    public function findMissingFields()
1625
    {
1626
        self::$logger->debug('>>findMissingFields()');
1627
1628
        $config = ConfigProvider::getInstance();
1629
1630
        if (method_exists($this, 'before_findMissingFields_callback')) {
1631
            $this->{'before_findMissingFields_callback'}();
1632
        }
1633
1634
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1635
        $provider->setRecord($this);
1636
        $missingFields = $provider->findMissingFields();
1637
1638
        if (method_exists($this, 'after_findMissingFields_callback')) {
1639
            $this->{'after_findMissingFields_callback'}();
1640
        }
1641
1642
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1643
1644
        return $missingFields;
1645
    }
1646
1647
    /**
1648
     * Getter for the TABLE_NAME, which should be set by a child of this class.
1649
     *
1650
     * @return string The table name in the database.
1651
     *
1652
     * @since 1.0
1653
     *
1654
     * @throws \Alpha\Exception\AlphaException
1655
     */
1656
    public function getTableName()
1657
    {
1658
        self::$logger->debug('>>getTableName()');
1659
1660
        $className = get_class($this);
1661
1662
        $tableName = $className::TABLE_NAME;
1663
1664
        if (!empty($tableName)) {
1665
            self::$logger->debug('<<getTableName ['.$tableName.']');
1666
1667
            return $tableName;
1668
        } else {
1669
            throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
1670
        }
1671
    }
1672
1673
    /**
1674
     * Method for getting the ID of the person who created this record.
1675
     *
1676
     * @return \Alpha\Model\Type\Integer The ID of the creator.
1677
     *
1678
     * @since 1.0
1679
     */
1680
    public function getCreatorId()
1681
    {
1682
        self::$logger->debug('>>getCreatorId()');
1683
        self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
1684
1685
        return $this->created_by;
1686
    }
1687
1688
    /**
1689
     * Method for getting the ID of the person who updated this record.
1690
     *
1691
     * @return \Alpha\Model\Type\Integer The ID of the updator.
1692
     *
1693
     * @since 1.0
1694
     */
1695
    public function getUpdatorId()
1696
    {
1697
        self::$logger->debug('>>getUpdatorId()');
1698
        self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
1699
1700
        return $this->updated_by;
1701
    }
1702
1703
    /**
1704
     * Method for getting the date/time of when the Record was created.
1705
     *
1706
     * @return \Alpha\Model\Type\Timestamp
1707
     *
1708
     * @since 1.0
1709
     */
1710
    public function getCreateTS()
1711
    {
1712
        self::$logger->debug('>>getCreateTS()');
1713
        self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
1714
1715
        return $this->created_ts;
1716
    }
1717
1718
    /**
1719
     * Method for getting the date/time of when the Record was last updated.
1720
     *
1721
     * @return \Alpha\Model\Type\Timestamp
1722
     *
1723
     * @since 1.0
1724
     */
1725
    public function getUpdateTS()
1726
    {
1727
        self::$logger->debug('>>getUpdateTS()');
1728
        self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
1729
1730
        return $this->updated_ts;
1731
    }
1732
1733
    /**
1734
     * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this record.
1735
     *
1736
     * @param string $attributeName The name of the attribute to not save.
1737
     *
1738
     * @since 1.0
1739
     */
1740
    public function markTransient($attributeName)
1741
    {
1742
        self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
1743
        self::$logger->debug('<<markTransient');
1744
        array_push($this->transientAttributes, $attributeName);
1745
    }
1746
1747
    /**
1748
     * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this record,
1749
     * ensuring that it will be saved on the next attempt.
1750
     *
1751
     * @param string $attributeName The name of the attribute to save.
1752
     *
1753
     * @since 1.0
1754
     */
1755
    public function markPersistent($attributeName)
1756
    {
1757
        self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
1758
        self::$logger->debug('<<markPersistent');
1759
        $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
1760
    }
1761
1762
    /**
1763
     * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this record.
1764
     *
1765
     * @param string $attribute1Name The first attribute to mark unique in the database.
1766
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1767
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1768
     *
1769
     * @since 1.0
1770
     */
1771
    protected function markUnique($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1772
    {
1773
        self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1774
1775
        if (empty($attribute2Name)) {
1776
            array_push($this->uniqueAttributes, $attribute1Name);
1777
        } else {
1778
            // Process composite unique keys: add them seperated by a + sign
1779
            if ($attribute3Name == '') {
1780
                $attributes = $attribute1Name.'+'.$attribute2Name;
1781
            } else {
1782
                $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
1783
            }
1784
1785
            array_push($this->uniqueAttributes, $attributes);
1786
        }
1787
1788
        self::$logger->debug('<<markUnique');
1789
    }
1790
1791
    /**
1792
     * Returns the array of names of unique attributes on this record.
1793
     *
1794
     * @return array
1795
     *
1796
     * @since 1.1
1797
     */
1798
    public function getUniqueAttributes()
1799
    {
1800
        self::$logger->debug('>>getUniqueAttributes()');
1801
        self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
1802
1803
        return $this->uniqueAttributes;
1804
    }
1805
1806
    /**
1807
     * Gets an array of all of the names of the active database indexes for this class.
1808
     *
1809
     * @return array An array of database indexes on this table.
1810
     *
1811
     * @since 1.0
1812
     *
1813
     * @throws \Alpha\Exception\AlphaException
1814
     */
1815
    public function getIndexes()
1816
    {
1817
        self::$logger->debug('>>getIndexes()');
1818
1819
        $config = ConfigProvider::getInstance();
1820
1821
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1822
        $provider->setRecord($this);
1823
        $indexNames = $provider->getIndexes();
1824
1825
        self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
1826
1827
        return $indexNames;
1828
    }
1829
1830
    /**
1831
     * Creates a foreign key constraint (index) in the database on the given attribute.
1832
     *
1833
     * @param string $attributeName         The name of the attribute to apply the index on.
1834
     * @param string $relatedClass          The name of the related class in the format "NameObject".
1835
     * @param string $relatedClassAttribute The name of the field to relate to on the related class.
1836
     * @param string $indexName             The optional name for the index, will calculate if not provided.
1837
     *
1838
     * @since 1.0
1839
     *
1840
     * @throws \Alpha\Exception\FailedIndexCreateException
1841
     */
1842
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
1843
    {
1844
        self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
1845
1846
        $config = ConfigProvider::getInstance();
1847
1848
        if (method_exists($this, 'before_createForeignIndex_callback')) {
1849
            $this->{'before_createForeignIndex_callback'}();
1850
        }
1851
1852
        $relatedRecord = new $relatedClass();
1853
        $tableName = $relatedRecord->getTableName();
1854
1855
        // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
1856
        if ($this->getTableName() == $tableName) {
1857
            self::$logger->debug('<<createForeignIndex');
1858
1859
            return;
1860
        }
1861
1862
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1863
        $provider->setRecord($this);
1864
        $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName);
1865
1866
        if (method_exists($this, 'after_createForeignIndex_callback')) {
1867
            $this->{'after_createForeignIndex_callback'}();
1868
        }
1869
1870
        self::$logger->debug('<<createForeignIndex');
1871
    }
1872
1873
    /**
1874
     * Creates a unique index in the database on the given attribute(s).
1875
     *
1876
     * @param string $attribute1Name The first attribute to mark unique in the database.
1877
     * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
1878
     * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
1879
     *
1880
     * @since 1.0
1881
     *
1882
     * @throws \Alpha\Exception\FailedIndexCreateException
1883
     */
1884
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
1885
    {
1886
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
1887
1888
        if (method_exists($this, 'before_createUniqueIndex_callback')) {
1889
            $this->{'before_createUniqueIndex_callback'}();
1890
        }
1891
1892
        $config = ConfigProvider::getInstance();
1893
1894
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
1895
        $provider->setRecord($this);
1896
        $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
1897
1898
        if (method_exists($this, 'after_createUniqueIndex_callback')) {
1899
            $this->{'before_createUniqueIndex_callback'}();
1900
        }
1901
1902
        self::$logger->debug('<<createUniqueIndex');
1903
    }
1904
1905
    /**
1906
     * Gets the data labels array.
1907
     *
1908
     * @return array An array of attribute labels.
1909
     *
1910
     * @since 1.0
1911
     */
1912
    public function getDataLabels()
1913
    {
1914
        self::$logger->debug('>>getDataLabels()');
1915
        self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
1916
1917
        return $this->dataLabels;
1918
    }
1919
1920
    /**
1921
     * Sets the data labels array.
1922
     *
1923
     * @param array $labels
1924
     *
1925
     * @throws \Alpha\Exception\IllegalArguementException
1926
     *
1927
     * @since 1.2
1928
     */
1929
    public function setDataLabels($labels)
1930
    {
1931
        self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
1932
1933
        if (is_array($labels)) {
1934
            $this->dataLabels = $labels;
1935
        } else {
1936
            throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
1937
        }
1938
1939
        self::$logger->debug('<<setDataLabels()');
1940
    }
1941
1942
    /**
1943
     * Gets the data label for the given attribute name.
1944
     *
1945
     * @param $att The attribute name to get the label for.
1946
     *
1947
     * @return string
1948
     *
1949
     * @since 1.0
1950
     *
1951
     * @throws \Alpha\Exception\IllegalArguementException
1952
     */
1953
    public function getDataLabel($att)
1954
    {
1955
        self::$logger->debug('>>getDataLabel(att=['.$att.'])');
1956
1957
        if (in_array($att, array_keys($this->dataLabels))) {
1958
            self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
1959
1960
            return $this->dataLabels[$att];
1961
        } else {
1962
            self::$logger->debug('<<getDataLabel');
1963
            throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
1964
        }
1965
    }
1966
1967
    /**
1968
     * Loops over the core and custom Record directories and builds an array of all of the Record class names in the system.
1969
     *
1970
     * @return array An array of business object class names.
1971
     *
1972
     * @since 1.0
1973
     */
1974
    public static function getRecordClassNames()
1975
    {
1976
        if (self::$logger == null) {
1977
            self::$logger = new Logger('ActiveRecord');
1978
        }
1979
        self::$logger->debug('>>getRecordClassNames()');
1980
1981
        $config = ConfigProvider::getInstance();
1982
1983
        $classNameArray = array();
1984
1985
        if (file_exists($config->get('app.root').'src/Model')) { // it is possible it has not been created yet...
1986
            // first get any custom records
1987
            $handle = opendir($config->get('app.root').'src/Model');
1988
1989
            // loop over the business object directory
1990
            while (false !== ($file = readdir($handle))) {
1991
                if (preg_match('/.php/', $file)) {
1992
                    $classname = 'Model\\'.mb_substr($file, 0, -4);
1993
1994
                    if (class_exists($classname)) {
1995
                        array_push($classNameArray, $classname);
1996
                    }
1997
                }
1998
            }
1999
        }
2000
2001
        // now loop over the core records provided with Alpha
2002
        if (file_exists($config->get('app.root').'Alpha/Model')) {
2003
            $handle = opendir($config->get('app.root').'Alpha/Model');
2004
        } else {
2005
            $handle = opendir($config->get('app.root').'vendor/alphadevx/alpha/Alpha/Model');
2006
        }
2007
2008
        // loop over the business object directory
2009
        while (false !== ($file = readdir($handle))) {
2010
            if (preg_match('/.php/', $file)) {
2011
                $classname = 'Alpha\\Model\\'.mb_substr($file, 0, -4);
2012
2013
                if (class_exists($classname) && substr($classname, 0, 24) != 'Alpha\\Model\\ActiveRecord') {
2014
                    array_push($classNameArray, $classname);
2015
                }
2016
            }
2017
        }
2018
2019
        asort($classNameArray);
2020
        self::$logger->debug('<<getRecordClassNames ['.var_export($classNameArray, true).']');
2021
2022
        return $classNameArray;
2023
    }
2024
2025
    /**
2026
     * Get the array of default attribute names.
2027
     *
2028
     * @return array An array of attribute names.
2029
     *
2030
     * @since 1.0
2031
     */
2032
    public function getDefaultAttributes()
2033
    {
2034
        self::$logger->debug('>>getDefaultAttributes()');
2035
        self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
2036
2037
        return $this->defaultAttributes;
2038
    }
2039
2040
    /**
2041
     * Get the array of transient attribute names.
2042
     *
2043
     * @return array An array of attribute names.
2044
     *
2045
     * @since 1.0
2046
     */
2047
    public function getTransientAttributes()
2048
    {
2049
        self::$logger->debug('>>getTransientAttributes()');
2050
        self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
2051
2052
        return $this->transientAttributes;
2053
    }
2054
2055
    /**
2056
     * Get the array of persistent attribute names, i.e. those that are saved in the database.
2057
     *
2058
     * @return array An array of attribute names.
2059
     *
2060
     * @since 1.0
2061
     */
2062
    public function getPersistentAttributes()
2063
    {
2064
        self::$logger->debug('>>getPersistentAttributes()');
2065
2066
        $attributes = array();
2067
2068
        // get the class attributes
2069
        $reflection = new ReflectionClass(get_class($this));
2070
        $properties = $reflection->getProperties();
2071
2072
        foreach ($properties as $propObj) {
2073
            $propName = $propObj->name;
2074
2075
            // filter transient attributes
2076
            if (!in_array($propName, $this->transientAttributes)) {
2077
                array_push($attributes, $propName);
2078
            }
2079
        }
2080
2081
        self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
2082
2083
        return $attributes;
2084
    }
2085
2086
    /**
2087
     * Setter for the Object ID (ID).
2088
     *
2089
     * @param int $ID The Object ID.
2090
     *
2091
     * @since 1.0
2092
     */
2093
    public function setID($ID)
2094
    {
2095
        self::$logger->debug('>>setID(ID=['.$ID.'])');
2096
        self::$logger->debug('<<setID');
2097
        $this->ID = $ID;
2098
    }
2099
2100
    /**
2101
     * Inspector to see if the business object is transient (not presently stored in the database).
2102
     *
2103
     * @return bool
2104
     *
2105
     * @since 1.0
2106
     */
2107
    public function isTransient()
2108
    {
2109
        self::$logger->debug('>>isTransient()');
2110
2111
        if (empty($this->ID) || !isset($this->ID) || $this->ID == '00000000000') {
2112
            self::$logger->debug('<<isTransient [true]');
2113
2114
            return true;
2115
        } else {
2116
            self::$logger->debug('<<isTransient [false]');
2117
2118
            return false;
2119
        }
2120
    }
2121
2122
    /**
2123
     * Get the last database query run on this object.
2124
     *
2125
     * @return string An SQL query string.
2126
     *
2127
     * @since 1.0
2128
     */
2129
    public function getLastQuery()
2130
    {
2131
        self::$logger->debug('>>getLastQuery()');
2132
        self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
2133
2134
        return $this->lastQuery;
2135
    }
2136
2137
    /**
2138
     * Unsets all of the attributes of this object to null.
2139
     *
2140
     * @since 1.0
2141
     */
2142
    private function clear()
2143
    {
2144
        self::$logger->debug('>>clear()');
2145
2146
        // get the class attributes
2147
        $reflection = new ReflectionClass(get_class($this));
2148
        $properties = $reflection->getProperties();
2149
2150
        foreach ($properties as $propObj) {
2151
            $propName = $propObj->name;
2152
            if (!$propObj->isPrivate()) {
2153
                unset($this->$propName);
2154
            }
2155
        }
2156
2157
        self::$logger->debug('<<clear');
2158
    }
2159
2160
    /**
2161
     * Reloads the object from the database, overwritting any attribute values in memory.
2162
     *
2163
     * @since 1.0
2164
     *
2165
     * @throws \Alpha\Exception\AlphaException
2166
     */
2167
    public function reload()
2168
    {
2169
        self::$logger->debug('>>reload()');
2170
2171
        if (!$this->isTransient()) {
2172
            $this->load($this->getID());
2173
        } else {
2174
            throw new AlphaException('Cannot reload transient object from database!');
2175
        }
2176
        self::$logger->debug('<<reload');
2177
    }
2178
2179
    /**
2180
     * Checks that a record exists for the Record in the database.
2181
     *
2182
     * @param int $ID The Object ID of the object we want to see whether it exists or not.
2183
     *
2184
     * @return bool
2185
     *
2186
     * @since 1.0
2187
     *
2188
     * @throws \Alpha\Exception\AlphaException
2189
     */
2190
    public function checkRecordExists($ID)
2191
    {
2192
        self::$logger->debug('>>checkRecordExists(ID=['.$ID.'])');
2193
2194
        if (method_exists($this, 'before_checkRecordExists_callback')) {
2195
            $this->{'before_checkRecordExists_callback'}();
2196
        }
2197
2198
        $config = ConfigProvider::getInstance();
2199
2200
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2201
        $provider->setRecord($this);
2202
        $recordExists = $provider->checkRecordExists($ID);
2203
2204
        if (method_exists($this, 'after_checkRecordExists_callback')) {
2205
            $this->{'after_checkRecordExists_callback'}();
2206
        }
2207
2208
        self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
2209
2210
        return $recordExists;
2211
    }
2212
2213
    /**
2214
     * Checks to see if the table name matches the classname, and if not if the table
2215
     * name matches the classname name of another record, i.e. the table is used to store
2216
     * multiple types of records.
2217
     *
2218
     * @return bool
2219
     *
2220
     * @since 1.0
2221
     *
2222
     * @throws \Alpha\Exception\BadTableNameException
2223
     */
2224
    public function isTableOverloaded()
2225
    {
2226
        self::$logger->debug('>>isTableOverloaded()');
2227
2228
        $config = ConfigProvider::getInstance();
2229
2230
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2231
        $provider->setRecord($this);
2232
        $isOverloaded = $provider->isTableOverloaded();
2233
2234
        self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
2235
2236
        return $isOverloaded;
2237
    }
2238
2239
    /**
2240
     * Starts a new database transaction.
2241
     *
2242
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2243
     *
2244
     * @since 1.0
2245
     *
2246
     * @throws \Alpha\Exception\AlphaException
2247
     */
2248
    public static function begin($record = null)
2249
    {
2250
        if (self::$logger == null) {
2251
            self::$logger = new Logger('ActiveRecord');
2252
        }
2253
        self::$logger->debug('>>begin()');
2254
2255
        $config = ConfigProvider::getInstance();
2256
2257
        if (isset($record)) {
2258
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2259
            $provider->setRecord($record);
2260
        } else {
2261
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2262
            $provider->setRecord(new Person());
2263
        }
2264
2265
        try {
2266
            $provider->begin();
2267
        } catch (\Exception $e) {
2268
            throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
2269
        }
2270
2271
        self::$logger->debug('<<begin');
2272
    }
2273
2274
    /**
2275
     * Commits the current database transaction.
2276
     *
2277
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2278
     *
2279
     * @since 1.0
2280
     *
2281
     * @throws \Alpha\Exception\FailedSaveException
2282
     */
2283
    public static function commit($record = null)
2284
    {
2285
        if (self::$logger == null) {
2286
            self::$logger = new Logger('ActiveRecord');
2287
        }
2288
        self::$logger->debug('>>commit()');
2289
2290
        $config = ConfigProvider::getInstance();
2291
2292
        if (isset($record)) {
2293
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2294
            $provider->setRecord($record);
2295
        } else {
2296
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2297
            $provider->setRecord(new Person());
2298
        }
2299
2300
        try {
2301
            $provider->commit();
2302
        } catch (\Exception $e) {
2303
            throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
2304
        }
2305
2306
        self::$logger->debug('<<commit');
2307
    }
2308
2309
    /**
2310
     * Aborts the current database transaction.
2311
     *
2312
     * @param ActiveRecord $record The ActiveRecord instance to pass to the database provider. Leave empty to have a new Person passed.
2313
     *
2314
     * @since 1.0
2315
     *
2316
     * @throws \Alpha\Exception\AlphaException
2317
     */
2318
    public static function rollback($record = null)
2319
    {
2320
        if (self::$logger == null) {
2321
            self::$logger = new Logger('ActiveRecord');
2322
        }
2323
        self::$logger->debug('>>rollback()');
2324
2325
        $config = ConfigProvider::getInstance();
2326
2327
        if (isset($record)) {
2328
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2329
            $provider->setRecord($record);
2330
        } else {
2331
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2332
            $provider->setRecord(new Person());
2333
        }
2334
2335
        try {
2336
            $provider->rollback();
2337
        } catch (\Exception $e) {
2338
            throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
2339
        }
2340
2341
        self::$logger->debug('<<rollback');
2342
    }
2343
2344
    /**
2345
     * Static method that tries to determine if the system database has been installed or not.
2346
     *
2347
     * @return bool
2348
     *
2349
     * @since 1.0
2350
     */
2351
    public static function isInstalled()
2352
    {
2353
        if (self::$logger == null) {
2354
            self::$logger = new Logger('ActiveRecord');
2355
        }
2356
        self::$logger->debug('>>isInstalled()');
2357
2358
        /*
2359
         * Install conditions are:
2360
         *
2361
         * 1. person table exists
2362
         * 2. rights table exists
2363
         */
2364
        if (self::checkRecordTableExists('Alpha\Model\Person') && self::checkRecordTableExists('Alpha\Model\Rights')) {
2365
            self::$logger->debug('<<isInstalled [true]');
2366
2367
            return true;
2368
        } else {
2369
            self::$logger->debug('<<isInstalled [false]');
2370
2371
            return false;
2372
        }
2373
    }
2374
2375
    /**
2376
     * Returns true if the Record has a Relation property called tags, false otherwise.
2377
     *
2378
     * @return bool
2379
     *
2380
     * @since 1.0
2381
     */
2382
    public function isTagged()
2383
    {
2384
        if (property_exists($this, 'taggedAttributes') && property_exists($this, 'tags') && $this->{'tags'} instanceof \Alpha\Model\Type\Relation) {
2385
            return true;
2386
        } else {
2387
            return false;
2388
        }
2389
    }
2390
2391
    /**
2392
     * Returns the contents of the taggedAttributes array, or an empty array if that does not exist.
2393
     *
2394
     * @return array
2395
     *
2396
     * @since 1.2.3
2397
     */
2398
    public function getTaggedAttributes()
2399
    {
2400
        if ($this->isTagged()) {
2401
            return $this->{'taggedAttributes'};
2402
        } else {
2403
            return array();
2404
        }
2405
    }
2406
2407
    /**
2408
     * Setter for the Record version number.
2409
     *
2410
     * @param int $versionNumber The version number.
2411
     *
2412
     * @since 1.0
2413
     */
2414
    private function setVersion($versionNumber)
2415
    {
2416
        $this->version_num->setValue($versionNumber);
2417
    }
2418
2419
    /**
2420
     * Cast a Record to another type of record.  A new Record will be returned with the same ID and
2421
     * version_num as the old record, so this is NOT a true cast but is a copy.  All attribute
2422
     * values will be copied accross.
2423
     *
2424
     * @param string                    $targetClassName     The fully-qualified name of the target Record class.
2425
     * @param \Alpha\Model\ActiveRecord $originalRecord      The original business object.
2426
     *
2427
     * @return \Alpha\Model\ActiveRecord The new business object resulting from the cast.
2428
     *
2429
     * @since 1.0
2430
     */
2431
    public function cast($targetClassName, $originalRecord)
2432
    {
2433
        $record = new $targetClassName();
2434
        $record->setID($originalRecord->getID());
2435
        $record->setVersion($originalRecord->getVersion());
2436
2437
        // get the class attributes
2438
        $originalRecordreflection = new ReflectionClass(get_class($originalRecord));
2439
        $originalRecordproperties = $originalRecordreflection->getProperties();
2440
        $newRecordreflection = new ReflectionClass($targetClassName);
2441
        $newRecordproperties = $newRecordreflection->getProperties();
2442
2443
        // copy the property values from the old Record to the new record
2444
2445
        if (count($originalRecordproperties) < count($newRecordproperties)) {
2446
            // the original Record is smaller, so loop over its properties
2447
            foreach ($originalRecordproperties as $propObj) {
2448
                $propName = $propObj->name;
2449
                if (!in_array($propName, $this->transientAttributes)) {
2450
                    $record->set($propName, $originalRecord->get($propName));
2451
                }
2452
            }
2453
        } else {
2454
            // the new Record is smaller, so loop over its properties
2455
            foreach ($newRecordproperties as $propObj) {
2456
                $propName = $propObj->name;
2457
                if (!in_array($propName, $this->transientAttributes)) {
2458
                    $record->set($propName, $originalRecord->get($propName));
2459
                }
2460
            }
2461
        }
2462
2463
        return $record;
2464
    }
2465
2466
    /**
2467
     * Returns the simple class name, stripped of the namespace.
2468
     *
2469
     * @return string
2470
     *
2471
     * @since 1.0
2472
     */
2473
    public function getFriendlyClassName()
2474
    {
2475
        $reflectClass = new ReflectionClass($this);
2476
2477
        return $reflectClass->getShortname();
2478
    }
2479
2480
    /**
2481
     * Check to see if an attribute exists on the record.
2482
     *
2483
     * @param string $attribute The attribute name.
2484
     *
2485
     * @return bool
2486
     *
2487
     * @since 1.0
2488
     */
2489
    public function hasAttribute($attribute)
2490
    {
2491
        return property_exists($this, $attribute);
2492
    }
2493
2494
    /**
2495
     * Stores the business object to the configured cache instance.
2496
     *
2497
     * @since 1.1
2498
     */
2499
    public function addToCache()
2500
    {
2501
        self::$logger->debug('>>addToCache()');
2502
        $config = ConfigProvider::getInstance();
2503
2504
        try {
2505
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2506
            $cache->set(get_class($this).'-'.$this->getID(), $this, 3600);
2507
        } catch (\Exception $e) {
2508
            self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').'] 
2509
                instance: ['.$e->getMessage().']');
2510
        }
2511
2512
        self::$logger->debug('<<addToCache');
2513
    }
2514
2515
    /**
2516
     * Removes the business object from the configured cache instance.
2517
     *
2518
     * @since 1.1
2519
     */
2520
    public function removeFromCache()
2521
    {
2522
        self::$logger->debug('>>removeFromCache()');
2523
        $config = ConfigProvider::getInstance();
2524
2525
        try {
2526
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2527
            $cache->delete(get_class($this).'-'.$this->getID());
2528
        } catch (\Exception $e) {
2529
            self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
2530
                instance: ['.$e->getMessage().']');
2531
        }
2532
2533
        self::$logger->debug('<<removeFromCache');
2534
    }
2535
2536
    /**
2537
     * Attempts to load the business object from the configured cache instance.
2538
     *
2539
     * @since 1.1
2540
     *
2541
     * @return bool
2542
     */
2543
    public function loadFromCache()
2544
    {
2545
        self::$logger->debug('>>loadFromCache()');
2546
        $config = ConfigProvider::getInstance();
2547
2548
        try {
2549
            $cache = ServiceFactory::getInstance($config->get('cache.provider.name'), 'Alpha\Util\Cache\CacheProviderInterface');
2550
            $record = $cache->get(get_class($this).'-'.$this->getID());
2551
2552
            if (!$record) {
2553
                self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getID().']');
2554
                self::$logger->debug('<<loadFromCache: [false]');
2555
2556
                return false;
2557
            } else {
2558
                // get the class attributes
2559
                $reflection = new ReflectionClass(get_class($this));
2560
                $properties = $reflection->getProperties();
2561
2562
                foreach ($properties as $propObj) {
2563
                    $propName = $propObj->name;
2564
2565
                    // filter transient attributes
2566
                    if (!in_array($propName, $this->transientAttributes)) {
2567
                        $this->set($propName, $record->get($propName, true));
2568
                    } elseif (!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
2569
                        $prop = $this->getPropObject($propName);
2570
2571
                        // handle the setting of ONE-TO-MANY relation values
2572
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
2573
                            $this->set($propObj->name, $this->getID());
2574
                        }
2575
                    }
2576
                }
2577
2578
                self::$logger->debug('<<loadFromCache: [true]');
2579
2580
                return true;
2581
            }
2582
        } catch (\Exception $e) {
2583
            self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
2584
             instance: ['.$e->getMessage().']');
2585
2586
            self::$logger->debug('<<loadFromCache: [false]');
2587
2588
            return false;
2589
        }
2590
    }
2591
2592
    /**
2593
     * Sets the last query executed on this business object.
2594
     *
2595
     * @param string $query
2596
     *
2597
     * @since 1.1
2598
     */
2599
    public function setLastQuery($query)
2600
    {
2601
        self::$logger->sql($query);
2602
        $this->lastQuery = $query;
2603
    }
2604
2605
    /**
2606
     * Re-initialize the static logger property on the Record after de-serialize, as PHP does
2607
     * not serialize static properties.
2608
     *
2609
     * @since 1.2
2610
     */
2611
    public function __wakeup()
2612
    {
2613
        if (self::$logger == null) {
2614
            self::$logger = new Logger(get_class($this));
2615
        }
2616
    }
2617
2618
    /**
2619
     * Sets maintainHistory attribute on this DAO.
2620
     *
2621
     * @param bool $maintainHistory
2622
     *
2623
     * @throws \Alpha\Exception\IllegalArguementException
2624
     *
2625
     * @since 1.2
2626
     */
2627
    public function setMaintainHistory($maintainHistory)
2628
    {
2629
        if (!is_bool($maintainHistory)) {
2630
            throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
2631
        }
2632
2633
        $this->maintainHistory = $maintainHistory;
2634
    }
2635
2636
    /**
2637
     * Gets the value of the  maintainHistory attribute.
2638
     *
2639
     * @return bool
2640
     *
2641
     * @since 1.2
2642
     */
2643
    public function getMaintainHistory()
2644
    {
2645
        return $this->maintainHistory;
2646
    }
2647
2648
    /**
2649
     * Return a hash array of the object containing attribute names and simplfied values.
2650
     *
2651
     * @return array
2652
     *
2653
     * @since  1.2.4
2654
     */
2655
    public function toArray()
2656
    {
2657
        // get the class attributes
2658
        $reflection = new ReflectionClass(get_class($this));
2659
        $properties = $reflection->getProperties();
2660
2661
        $propArray = array();
2662
2663
        foreach ($properties as $propObj) {
2664
            $propName = $propObj->name;
2665
2666
            if (!in_array($propName, $this->transientAttributes)) {
2667
                $val = $this->get($propName);
2668
2669
                if (is_object($val)) {
2670
                    $val = $val->getValue();
2671
                }
2672
2673
                $propArray[$propName] = $val;
2674
            }
2675
        }
2676
2677
        return $propArray;
2678
    }
2679
2680
    /**
2681
     * Check to see if the configured database exists.
2682
     *
2683
     * @return bool
2684
     *
2685
     * @since 2.0
2686
     */
2687
    public static function checkDatabaseExists()
2688
    {
2689
        $config = ConfigProvider::getInstance();
2690
2691
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2692
        $provider->setRecord(new Person());
2693
2694
        return $provider->checkDatabaseExists();
2695
    }
2696
2697
    /**
2698
     * Creates the configured database.
2699
     *
2700
     * @throws \Alpha\Exception\AlphaException
2701
     *
2702
     * @since 2.0
2703
     */
2704
    public static function createDatabase()
2705
    {
2706
        $config = ConfigProvider::getInstance();
2707
2708
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2709
        $provider->setRecord(new Person());
2710
        $provider->createDatabase();
2711
    }
2712
2713
    /**
2714
     * Drops the configured database.
2715
     *
2716
     * @throws \Alpha\Exception\AlphaException
2717
     *
2718
     * @since 2.0
2719
     */
2720
    public static function dropDatabase()
2721
    {
2722
        $config = ConfigProvider::getInstance();
2723
2724
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2725
        $provider->setRecord(new Person());
2726
        $provider->dropDatabase();
2727
    }
2728
2729
    /**
2730
     * Backup the configured database.
2731
     *
2732
     * @param string $targetFile The file that the backup data will be written to.
2733
     *
2734
     * @throws \Alpha\Exception\AlphaException
2735
     *
2736
     * @since 3.0
2737
     */
2738
    public static function backupDatabase($targetFile)
2739
    {
2740
        $config = ConfigProvider::getInstance();
2741
2742
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
2743
        $provider->setRecord(new Person());
2744
        $provider->backupDatabase($targetFile);
2745
    }
2746
}
2747