Completed
Push — develop ( 7610d7...67789b )
by John
03:16
created

ActiveRecord::backupDatabase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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