Completed
Branch release/3.0.0-release (ef4781)
by John
03:23
created

ActiveRecord::save()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 40
rs 8.6577
cc 6
nc 18
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\Util\Config\ConfigProvider;
11
use Alpha\Util\Logging\Logger;
12
use Alpha\Util\Service\ServiceFactory;
13
use Alpha\Exception\AlphaException;
14
use Alpha\Exception\FailedSaveException;
15
use Alpha\Exception\FailedDeleteException;
16
use Alpha\Exception\ValidationException;
17
use Alpha\Exception\RecordNotFoundException;
18
use Alpha\Exception\IllegalArguementException;
19
use Alpha\Exception\LockingException;
20
use Alpha\Exception\NotImplementedException;
21
use ReflectionClass;
22
use ReflectionProperty;
23
24
/**
25
 * Base active record class definition providing database storage via the configured provider.
26
 *
27
 * @since 1.0
28
 *
29
 * @author John Collins <[email protected]>
30
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
31
 * @copyright Copyright (c) 2018, John Collins (founder of Alpha Framework).
32
 * All rights reserved.
33
 *
34
 * <pre>
35
 * Redistribution and use in source and binary forms, with or
36
 * without modification, are permitted provided that the
37
 * following conditions are met:
38
 *
39
 * * Redistributions of source code must retain the above
40
 *   copyright notice, this list of conditions and the
41
 *   following disclaimer.
42
 * * Redistributions in binary form must reproduce the above
43
 *   copyright notice, this list of conditions and the
44
 *   following disclaimer in the documentation and/or other
45
 *   materials provided with the distribution.
46
 * * Neither the name of the Alpha Framework nor the names
47
 *   of its contributors may be used to endorse or promote
48
 *   products derived from this software without specific
49
 *   prior written permission.
50
 *
51
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
52
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
53
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
54
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
56
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
57
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
58
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
59
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
60
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
61
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
62
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
63
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64
 * </pre>
65
 */
66
abstract class ActiveRecord
67
{
68
    /**
69
     * The object ID.
70
     *
71
     * @var int
72
     *
73
     * @since 1.0
74
     */
75
    protected $ID;
76
77
    /**
78
     * The last database query run by this object.  Useful for tracing an error.
79
     *
80
     * @var string
81
     *
82
     * @since 1.0
83
     */
84
    protected $lastQuery;
85
86
    /**
87
     * The version number of the object, used for locking mechanism.
88
     *
89
     * @var \Alpha\Model\Type\Integer
90
     *
91
     * @since 1.0
92
     */
93
    protected $version_num;
94
95
    /**
96
     * The timestamp of creation.
97
     *
98
     * @var \Alpha\Model\Type\Timestamp
99
     *
100
     * @since 1.0
101
     */
102
    protected $created_ts;
103
104
    /**
105
     * The ID of the person who created this record.
106
     *
107
     * @var \Alpha\Model\Type\Integer
108
     *
109
     * @since 1.0
110
     */
111
    protected $created_by;
112
113
    /**
114
     * The timestamp of the last update.
115
     *
116
     * @var \Alpha\Model\Type\Timestamp
117
     *
118
     * @since 1.0
119
     */
120
    protected $updated_ts;
121
122
    /**
123
     * The ID of the person who last updated this record.
124
     *
125
     * @var \Alpha\Model\Type\Integer
126
     *
127
     * @since 1.0
128
     */
129
    protected $updated_by;
130
131
    /**
132
     * An array of the names of all of the default attributes of a persistent Record defined in this class.
133
     *
134
     * @var array
135
     *
136
     * @since 1.0
137
     */
138
    protected $defaultAttributes = array('ID', 'lastQuery', 'version_num', 'dataLabels', 'created_ts', 'created_by', 'updated_ts', 'updated_by', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
139
140
    /**
141
     * An array of the names of all of the transient attributes of a persistent Record which are not saved to the DB.
142
     *
143
     * @var array
144
     *
145
     * @since 1.0
146
     */
147
    protected $transientAttributes = array('lastQuery', 'dataLabels', 'defaultAttributes', 'transientAttributes', 'uniqueAttributes', 'TABLE_NAME', 'logger');
148
149
    /**
150
     * An array of the uniquely-constained attributes of this persistent record.
151
     *
152
     * @var array
153
     *
154
     * @since 1.0
155
     */
156
    protected $uniqueAttributes = array();
157
158
    /**
159
     * An array of the data labels used for displaying class attributes.
160
     *
161
     * @var array
162
     *
163
     * @since 1.0
164
     */
165
    protected $dataLabels = array();
166
167
    /**
168
     * Trace logger.
169
     *
170
     * @var \Alpha\Util\Logging\Logger
171
     *
172
     * @since 1.0
173
     */
174
    private static $logger = null;
175
176
    /**
177
     * Determines if we will maintain a _history table for this record (default is false).
178
     *
179
     * @var bool
180
     *
181
     * @since 1.2
182
     */
183
    private $maintainHistory = false;
184
185
    /**
186
     * The constructor which sets up some housekeeping attributes.
187
     *
188
     * @since 1.0
189
     */
190
    public function __construct()
191
    {
192
        self::$logger = new Logger('ActiveRecord');
193
        self::$logger->debug('>>__construct()');
194
195
        $config = ConfigProvider::getInstance();
196
        $sessionProvider = $config->get('session.provider.name');
197
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
198
199
        set_exception_handler('Alpha\Util\ErrorHandlers::catchException');
200
        set_error_handler('Alpha\Util\ErrorHandlers::catchError', $config->get('php.error.log.level'));
201
202
        $this->version_num = new Integer(0);
203
        $this->created_ts = new Timestamp(date('Y-m-d H:i:s'));
204
        $person_ID = ($session->get('currentUser') != null ? $session->get('currentUser')->getID() : 0);
205
        $this->created_by = new Integer($person_ID);
206
        $this->updated_ts = new Timestamp(date('Y-m-d H:i:s'));
207
        $this->updated_by = new Integer($person_ID);
208
209
        self::$logger->debug('<<__construct');
210
    }
211
212
    /**
213
     * Disconnects the current database connection if one exists.
214
     *
215
     * @since 1.0
216
     */
217
    public static function disconnect()
218
    {
219
        $config = ConfigProvider::getInstance();
220
221
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
222
        $provider->disconnect();
223
    }
224
225
    /**
226
     * Returns a 2d array, where each element in the array is another array representing a database row.
227
     *
228
     * @param string $sqlQuery
229
     *
230
     * @return array
231
     *
232
     * @since 1.1
233
     *
234
     * @throws \Alpha\Exception\CustomQueryException
235
     */
236
    public function query($sqlQuery)
237
    {
238
        self::$logger->debug('>>query(sqlQuery=['.$sqlQuery.'])');
239
240
        $config = ConfigProvider::getInstance();
241
242
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
243
        $provider->setRecord($this);
244
        $result = $provider->query($sqlQuery);
245
246
        self::$logger->debug('<<query ['.print_r($result, true).']');
247
248
        return $result;
249
    }
250
251
    /**
252
     * Populates the child object with the properties retrived from the database for the object $ID.
253
     *
254
     * @param int $ID     The object ID of the business object to load.
255
     * @param int $version Optionaly, provide the version to load that version from the [tablename]_history table.
256
     *
257
     * @since 1.0
258
     *
259
     * @throws \Alpha\Exception\RecordNotFoundException
260
     */
261
    public function load($ID, $version = 0)
262
    {
263
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
264
265
        if (method_exists($this, 'before_load_callback')) {
266
            $this->{'before_load_callback'}();
267
        }
268
269
        $config = ConfigProvider::getInstance();
270
271
        $this->ID = $ID;
272
273
        if ($config->get('cache.provider.name') != '' && $this->loadFromCache()) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
274
            // Record was found in cache
275
        } else {
276
            $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
277
            $provider->setRecord($this);
278
            $provider->load($ID, $version);
279
280
            if ($config->get('cache.provider.name') != '') {
281
                $this->addToCache();
282
            }
283
        }
284
285
        $this->setEnumOptions();
286
287
        if (method_exists($this, 'after_load_callback')) {
288
            $this->{'after_load_callback'}();
289
        }
290
291
        self::$logger->debug('<<load');
292
    }
293
294
    /**
295
     * Load all old versions (if any) of this record from the [tablename]_history table.
296
     *
297
     * @param int $ID The object ID of the record to load.
298
     *
299
     * @return array An array containing objects of this type of record object, order by version.
300
     *
301
     * @since 2.0
302
     *
303
     * @throws \Alpha\Exception\RecordFoundException
304
     */
305
    public function loadAllOldVersions($ID)
306
    {
307
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
308
309
        $config = ConfigProvider::getInstance();
310
311
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
312
        $provider->setRecord($this);
313
        $objects = $provider->loadAllOldVersions($ID);
314
315
        self::$logger->debug('<<loadAllOldVersions['.count($objects).']');
316
317
        return $objects;
318
    }
319
320
    /**
321
     * Populates the child object from the database table by the given attribute value.
322
     *
323
     * @param string $attribute       The name of the attribute to load the object by.
324
     * @param string $value           The value of the attribute to load the object by.
325
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
326
     * @param array  $loadAttributes  The attributes to load from the database to this object (leave blank to load all attributes)
327
     *
328
     * @since 1.0
329
     *
330
     * @throws \Alpha\Exception\RecordNotFoundException
331
     */
332
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
333
    {
334
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
335
            loadAttributes=['.var_export($loadAttributes, true).'])');
336
337
        if (method_exists($this, 'before_loadByAttribute_callback')) {
338
            $this->{'before_loadByAttribute_callback'}();
339
        }
340
341
        $config = ConfigProvider::getInstance();
342
343
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
344
        $provider->setRecord($this);
345
        $provider->loadByAttribute($attribute, $value, $ignoreClassType, $loadAttributes);
346
347
        $this->setEnumOptions();
348
349
        if ($config->get('cache.provider.name') != '' && count($loadAttributes) == 0) { // we will only cache fully-populated records
350
            $this->addToCache();
351
        }
352
353
        if (method_exists($this, 'after_loadByAttribute_callback')) {
354
            $this->{'after_loadByAttribute_callback'}();
355
        }
356
357
        self::$logger->debug('<<loadByAttribute');
358
    }
359
360
    /**
361
     * Loads all of the objects of this class into an array which is returned.
362
     *
363
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
364
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
365
     * @param string $orderBy         The name of the field to sort the objects by.
366
     * @param string $order           The order to sort the objects by.
367
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
368
     *
369
     * @return array An array containing objects of this type of business object.
370
     *
371
     * @since 1.0
372
     *
373
     * @throws \Alpha\Exception\RecordNotFoundException
374
     */
375
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
376
    {
377
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
378
379
        if (method_exists($this, 'before_loadAll_callback')) {
380
            $this->{'before_loadAll_callback'}();
381
        }
382
383
        $config = ConfigProvider::getInstance();
384
385
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
386
        $provider->setRecord($this);
387
        $objects = $provider->loadAll($start, $limit, $orderBy, $order, $ignoreClassType);
388
389
        if (method_exists($this, 'after_loadAll_callback')) {
390
            $this->{'after_loadAll_callback'}();
391
        }
392
393
        self::$logger->debug('<<loadAll ['.count($objects).']');
394
395
        return $objects;
396
    }
397
398
    /**
399
     * Loads all of the objects of this class by the specified attribute into an array which is returned.
400
     *
401
     * @param string $attribute       The attribute to load the objects by.
402
     * @param string $value           The value of the attribute to load the objects by.
403
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
404
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
405
     * @param string $orderBy         The name of the field to sort the objects by.
406
     * @param string $order           The order to sort the objects by.
407
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
408
     * @param array  $constructorArgs An optional array of contructor arguements to pass to the records that will be generated and returned.  Supports a maximum of 5 arguements.
409
     *
410
     * @return array An array containing objects of this type of business object.
411
     *
412
     * @since 1.0
413
     *
414
     * @throws \Alpha\Exception\RecordNotFoundException
415
     * @throws \Alpha\Exception\IllegalArguementException
416
     */
417
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
418
    {
419
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
420
421
        if (method_exists($this, 'before_loadAllByAttribute_callback')) {
422
            $this->{'before_loadAllByAttribute_callback'}();
423
        }
424
425
        $config = ConfigProvider::getInstance();
426
427
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
428
        $provider->setRecord($this);
429
        $objects = $provider->loadAllByAttribute($attribute, $value, $start, $limit, $orderBy, $order, $ignoreClassType);
430
431
        if (method_exists($this, 'after_loadAllByAttribute_callback')) {
432
            $this->{'after_loadAllByAttribute_callback'}();
433
        }
434
435
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
436
437
        return $objects;
438
    }
439
440
    /**
441
     * Loads all of the objects of this class by the specified attributes into an array which is returned.
442
     *
443
     * @param array  $attributes      The attributes to load the objects by.
444
     * @param array  $values          The values of the attributes to load the objects by.
445
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
446
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
447
     * @param string $orderBy         The name of the field to sort the objects by.
448
     * @param string $order           The order to sort the objects by.
449
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
450
     *
451
     * @return array An array containing objects of this type of business object.
452
     *
453
     * @since 1.0
454
     *
455
     * @throws \Alpha\Exception\RecordNotFoundException
456
     * @throws \Alpha\Exception\IllegalArguementException
457
     */
458
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
459
    {
460
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
461
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
462
463
        if (method_exists($this, 'before_loadAllByAttributes_callback')) {
464
            $this->{'before_loadAllByAttributes_callback'}();
465
        }
466
467
        $config = ConfigProvider::getInstance();
468
469
        if (!is_array($attributes) || !is_array($values)) {
470
            throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).
471
                '] provided to loadAllByAttributes');
472
        }
473
474
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
475
        $provider->setRecord($this);
476
        $objects = $provider->loadAllByAttributes($attributes, $values, $start, $limit, $orderBy, $order, $ignoreClassType);
477
478
        if (method_exists($this, 'after_loadAllByAttributes_callback')) {
479
            $this->{'after_loadAllByAttributes_callback'}();
480
        }
481
482
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
483
484
        return $objects;
485
    }
486
487
    /**
488
     * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
489
     *
490
     * @param string $date            The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
491
     * @param int    $start           The start of the SQL LIMIT clause, useful for pagination.
492
     * @param int    $limit           The amount (limit) of objects to load, useful for pagination.
493
     * @param string $orderBy         The name of the field to sort the objects by.
494
     * @param string $order           The order to sort the objects by.
495
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
496
     *
497
     * @return array An array containing objects of this type of business object.
498
     *
499
     * @since 1.0
500
     *
501
     * @throws \Alpha\Exception\RecordNotFoundException
502
     */
503
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
504
    {
505
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
506
507
        if (method_exists($this, 'before_loadAllByDayUpdated_callback')) {
508
            $this->{'before_loadAllByDayUpdated_callback'}();
509
        }
510
511
        $config = ConfigProvider::getInstance();
512
513
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
514
        $provider->setRecord($this);
515
        $objects = $provider->loadAllByDayUpdated($date, $start, $limit, $orderBy, $order, $ignoreClassType);
516
517
        if (method_exists($this, 'after_loadAllByDayUpdated_callback')) {
518
            $this->{'after_loadAllByDayUpdated_callback'}();
519
        }
520
521
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
522
523
        return $objects;
524
    }
525
526
    /**
527
     * Loads all of the specified attribute values of this class by the specified attribute into an
528
     * array which is returned.
529
     *
530
     * @param string $attribute       The attribute name to load the field values by.
531
     * @param string $value           The value of the attribute to load the field values by.
532
     * @param string $returnAttribute The name of the attribute to return.
533
     * @param string $order           The order to sort the records by.
534
     * @param bool   $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
535
     *
536
     * @return array An array of field values.
537
     *
538
     * @since 1.0
539
     *
540
     * @throws \Alpha\Exception\RecordNotFoundException
541
     */
542
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
543
    {
544
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
545
546
        $config = ConfigProvider::getInstance();
547
548
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
549
        $provider->setRecord($this);
550
        $values = $provider->loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order, $ignoreClassType);
551
552
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
553
554
        return $values;
555
    }
556
557
    /**
558
     * Saves the object.  If $this->ID is empty or null it will INSERT, otherwise UPDATE.
559
     *
560
     * @since 1.0
561
     *
562
     * @throws \Alpha\Exception\FailedSaveException
563
     * @throws \Alpha\Exception\LockingException
564
     * @throws \Alpha\Exception\ValidationException
565
     */
566
    public function save()
567
    {
568
        self::$logger->debug('>>save()');
569
570
        if (method_exists($this, 'before_save_callback')) {
571
            $this->{'before_save_callback'}();
572
        }
573
574
        $config = ConfigProvider::getInstance();
575
576
        // firstly we will validate the object before we try to save it
577
        $this->validate();
578
579
        $sessionProvider = $config->get('session.provider.name');
580
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
581
582
        if ($this->getVersion() != $this->getVersionNumber()->getValue()) {
583
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
584
        }
585
586
        // set the "updated by" fields, we can only set the user id if someone is logged in
587
        if ($session->get('currentUser') != null) {
588
            $this->set('updated_by', $session->get('currentUser')->getID());
589
        }
590
591
        $this->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
592
593
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
594
        $provider->setRecord($this);
595
        $provider->save();
596
597
        if ($config->get('cache.provider.name') != '') {
598
            $this->removeFromCache();
599
            $this->addToCache();
600
        }
601
602
        if (method_exists($this, 'after_save_callback')) {
603
            $this->{'after_save_callback'}();
604
        }
605
    }
606
607
    /**
608
     * Saves the field specified with the value supplied.  Only works for persistent records.  Note that no Alpha type
609
     * validation is performed with this method!
610
     *
611
     * @param string $attribute The name of the attribute to save.
612
     * @param mixed  $value     The value of the attribute to save.
613
     *
614
     * @since 1.0
615
     *
616
     * @throws \Alpha\Exception\IllegalArguementException
617
     * @throws \Alpha\Exception\FailedSaveException
618
     */
619
    public function saveAttribute($attribute, $value)
620
    {
621
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
622
623
        if (method_exists($this, 'before_saveAttribute_callback')) {
624
            $this->{'before_saveAttribute_callback'}();
625
        }
626
627
        $config = ConfigProvider::getInstance();
628
629
        if (!isset($this->$attribute)) {
630
            throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
631
        }
632
633
        if ($this->isTransient()) {
634
            throw new FailedSaveException('Cannot perform saveAttribute method on transient record!');
635
        }
636
637
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
638
        $provider->setRecord($this);
639
        $provider->saveAttribute($attribute, $value);
640
641
        if ($config->get('cache.provider.name') != '') {
642
            $this->removeFromCache();
643
            $this->addToCache();
644
        }
645
646
        if (method_exists($this, 'after_saveAttribute_callback')) {
647
            $this->{'after_saveAttribute_callback'}();
648
        }
649
650
        self::$logger->debug('<<saveAttribute');
651
    }
652
653
    /**
654
     * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
655
     *
656
     * @since 1.2
657
     *
658
     * @throws \Alpha\Exception\FailedSaveException
659
     */
660
    public function saveHistory()
661
    {
662
        self::$logger->debug('>>saveHistory()');
663
664
        if (method_exists($this, 'before_saveHistory_callback')) {
665
            $this->{'before_saveHistory_callback'}();
666
        }
667
668
        $config = ConfigProvider::getInstance();
669
670
        $provider = ServiceFactory::getInstance($config->get('db.provider.name'), 'Alpha\Model\ActiveRecordProviderInterface');
671
        $provider->setRecord($this);
672
        $provider->saveHistory();
673
674
        if (method_exists($this, 'after_saveHistory_callback')) {
675
            $this->{'after_saveHistory_callback'}();
676
        }
677
    }
678
679
    /**
680
     * Validates the object to be saved.
681
     *
682
     * @since 1.0
683
     *
684
     * @throws \Alpha\Exception\ValidationException
685
     */
686
    protected function validate()
687
    {
688
        self::$logger->debug('>>validate()');
689
690
        if (method_exists($this, 'before_validate_callback')) {
691
            $this->{'before_validate_callback'}();
692
        }
693
694
        // get the class attributes
695
        $reflection = new ReflectionClass(get_class($this));
696
        $properties = $reflection->getProperties();
697
698
        foreach ($properties as $propObj) {
699
            $propName = $propObj->name;
700
            if (!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
701
                $propClass = new ReflectionClass($this->getPropObject($propName));
702
                $propClass = $propClass->getShortname();
703
                if (mb_strtoupper($propClass) != 'ENUM' &&
704
                mb_strtoupper($propClass) != 'DENUM' &&
705
                mb_strtoupper($propClass) != 'DENUMITEM' &&
706
                mb_strtoupper($propClass) != 'BOOLEAN') {
707
                    if ($this->getPropObject($propName) != false && !preg_match($this->getPropObject($propName)->getRule(), $this->getPropObject($propName)->getValue())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getRule() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Date, Alpha\Model\Type\Double, Alpha\Model\Type\Integer, Alpha\Model\Type\Relation, Alpha\Model\Type\SmallText, Alpha\Model\Type\Text, Alpha\Model\Type\Timestamp. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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