Completed
Push — develop ( 67789b...2bd525 )
by John
03:25
created

ActiveRecordProviderSQLite::checkIndexes()   F

Complexity

Conditions 27
Paths 2912

Size

Total Lines 99
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 99
c 0
b 0
f 0
rs 2
cc 27
eloc 58
nc 2912
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\Integer;
6
use Alpha\Model\Type\Timestamp;
7
use Alpha\Model\Type\DEnum;
8
use Alpha\Model\Type\Relation;
9
use Alpha\Model\Type\RelationLookup;
10
use Alpha\Model\Type\Double;
11
use Alpha\Model\Type\Text;
12
use Alpha\Model\Type\SmallText;
13
use Alpha\Model\Type\Date;
14
use Alpha\Model\Type\Enum;
15
use Alpha\Model\Type\Boolean;
16
use Alpha\Util\Config\ConfigProvider;
17
use Alpha\Util\Logging\Logger;
18
use Alpha\Util\Service\ServiceFactory;
19
use Alpha\Exception\AlphaException;
20
use Alpha\Exception\FailedSaveException;
21
use Alpha\Exception\FailedDeleteException;
22
use Alpha\Exception\FailedIndexCreateException;
23
use Alpha\Exception\LockingException;
24
use Alpha\Exception\ValidationException;
25
use Alpha\Exception\CustomQueryException;
26
use Alpha\Exception\RecordNotFoundException;
27
use Alpha\Exception\BadTableNameException;
28
use Alpha\Exception\NotImplementedException;
29
use Alpha\Exception\PHPException;
30
use Alpha\Exception\ResourceNotAllowedException;
31
use Alpha\Exception\IllegalArguementException;
32
use Exception;
33
use SQLite3Stmt;
34
use SQLite3;
35
use ReflectionClass;
36
37
/**
38
 * SQLite active record provider (uses the SQLite3 native API in PHP).
39
 *
40
 * @since 1.2
41
 *
42
 * @author John Collins <[email protected]>
43
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
44
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
45
 * All rights reserved.
46
 *
47
 * <pre>
48
 * Redistribution and use in source and binary forms, with or
49
 * without modification, are permitted provided that the
50
 * following conditions are met:
51
 *
52
 * * Redistributions of source code must retain the above
53
 *   copyright notice, this list of conditions and the
54
 *   following disclaimer.
55
 * * Redistributions in binary form must reproduce the above
56
 *   copyright notice, this list of conditions and the
57
 *   following disclaimer in the documentation and/or other
58
 *   materials provided with the distribution.
59
 * * Neither the name of the Alpha Framework nor the names
60
 *   of its contributors may be used to endorse or promote
61
 *   products derived from this software without specific
62
 *   prior written permission.
63
 *
64
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
65
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
66
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
67
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
68
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
69
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
70
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
71
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
72
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
73
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
74
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
75
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
76
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
77
 * </pre>
78
 */
79
class ActiveRecordProviderSQLite implements ActiveRecordProviderInterface
80
{
81
    /**
82
     * Trace logger.
83
     *
84
     * @var \Alpha\Util\Logging\Logger
85
     *
86
     * @since 1.2
87
     */
88
    private static $logger = null;
89
90
    /**
91
     * Database connection.
92
     *
93
     * @var SQLite3
94
     *
95
     * @since 1.2
96
     */
97
    private static $connection;
98
99
    /**
100
     * The business object that we are mapping back to.
101
     *
102
     * @var \Alpha\Model\ActiveRecord
103
     *
104
     * @since 1.2
105
     */
106
    private $record;
107
108
    /**
109
     * An array of new foreign keys that need to be created.
110
     *
111
     * @var array
112
     *
113
     * @since 2.0.1
114
     */
115
    private $foreignKeys = array();
116
117
    /**
118
     * The constructor.
119
     *
120
     * @since 1.2
121
     */
122
    public function __construct()
123
    {
124
        self::$logger = new Logger('ActiveRecordProviderSQLite');
125
        self::$logger->debug('>>__construct()');
126
127
        self::$logger->debug('<<__construct');
128
    }
129
130
    /**
131
     * (non-PHPdoc).
132
     *
133
     * @see Alpha\Model\ActiveRecordProviderInterface::getConnection()
134
     */
135
    public static function getConnection()
136
    {
137
        $config = ConfigProvider::getInstance();
138
139
        if (!isset(self::$connection)) {
140
            try {
141
                self::$connection = new SQLite3($config->get('db.file.path'));
142
            } catch (\Exception $e) {
143
                self::$logger->fatal('Could not open SQLite database: ['.$e->getMessage().']');
144
            }
145
        }
146
147
        return self::$connection;
148
    }
149
150
    /**
151
     * (non-PHPdoc).
152
     *
153
     * @see Alpha\Model\ActiveRecordProviderInterface::disconnect()
154
     */
155
    public static function disconnect()
156
    {
157
        if (isset(self::$connection)) {
158
            self::$connection->close();
159
            self::$connection = null;
160
        }
161
    }
162
163
    /**
164
     * (non-PHPdoc).
165
     *
166
     * @see Alpha\Model\ActiveRecordProviderInterface::getLastDatabaseError()
167
     */
168
    public static function getLastDatabaseError()
169
    {
170
        self::$connection->lastErrorMsg();
171
    }
172
173
    /**
174
     * (non-PHPdoc).
175
     *
176
     * @see Alpha\Model\ActiveRecordProviderInterface::query()
177
     */
178
    public function query($sqlQuery)
179
    {
180
        $this->record->setLastQuery($sqlQuery);
181
182
        $resultArray = array();
183
184
        if (!$result = self::getConnection()->query($sqlQuery)) {
185
            throw new CustomQueryException('Failed to run the custom query, SQLite error is ['.self::getLastDatabaseError().'], query ['.$sqlQuery.']');
186
        } else {
187
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
188
                array_push($resultArray, $row);
189
            }
190
191
            return $resultArray;
192
        }
193
    }
194
195
    /**
196
     * (non-PHPdoc).
197
     *
198
     * @see Alpha\Model\ActiveRecordProviderInterface::load()
199
     */
200
    public function load($ID, $version = 0)
201
    {
202
        self::$logger->debug('>>load(ID=['.$ID.'], version=['.$version.'])');
203
204
        $attributes = $this->record->getPersistentAttributes();
205
        $fields = '';
206
        foreach ($attributes as $att) {
207
            $fields .= $att.',';
208
        }
209
        $fields = mb_substr($fields, 0, -1);
210
211
        if ($version > 0) {
212
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().'_history WHERE ID = :ID AND version_num = :version LIMIT 1;';
213
        } else {
214
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE ID = :ID LIMIT 1;';
215
        }
216
        $this->record->setLastQuery($sqlQuery);
217
218
        try {
219
            $stmt = self::getConnection()->prepare($sqlQuery);
220
221
            $row = array();
222
223
            if ($version > 0) {
224
                $stmt->bindValue(':version', $version, SQLITE3_INTEGER);
225
            }
226
227
            $stmt->bindValue(':ID', $ID, SQLITE3_INTEGER);
228
229
            $result = $stmt->execute();
230
231
            // there should only ever be one (or none)
232
            $row = $result->fetchArray(SQLITE3_ASSOC);
233
234
            $stmt->close();
235
        } catch (PHPException $e) {
236
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
237
            if (!$this->record->checkTableExists()) {
238
                $this->record->makeTable();
239
240
                throw new RecordNotFoundException('Failed to load object of ID ['.$ID.'], table ['.$this->record->getTableName().'] did not exist so had to create!');
241
            }
242
243
            return;
244
        }
245
246
        if (!isset($row['ID']) || $row['ID'] < 1) {
247
            self::$logger->debug('<<load');
248
            throw new RecordNotFoundException('Failed to load object of ID ['.$ID.'] not found in database.');
249
        }
250
251
        // get the class attributes
252
        $reflection = new ReflectionClass(get_class($this->record));
253
        $properties = $reflection->getProperties();
254
255
        try {
256
            foreach ($properties as $propObj) {
257
                $propName = $propObj->name;
258
259
                // filter transient attributes
260
                if (!in_array($propName, $this->record->getTransientAttributes())) {
261
                    $this->record->set($propName, $row[$propName]);
262
                } elseif (!$propObj->isPrivate() && $this->record->getPropObject($propName) instanceof Relation) {
263
                    $prop = $this->record->getPropObject($propName);
264
265
                    // handle the setting of ONE-TO-MANY relation values
266
                    if ($prop->getRelationType() == 'ONE-TO-MANY') {
267
                        $this->record->set($propObj->name, $this->record->getID());
268
                    }
269
270
                    // handle the setting of MANY-TO-ONE relation values
271
                    if ($prop->getRelationType() == 'MANY-TO-ONE' && isset($row[$propName])) {
272
                        $this->record->set($propObj->name, $row[$propName]);
273
                    }
274
                }
275
            }
276
        } catch (IllegalArguementException $e) {
277
            self::$logger->warn('Bad data stored in the table ['.$this->record->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
278
        } catch (PHPException $e) {
279
            // it is possible that the load failed due to the table not being up-to-date
280
            if ($this->record->checkTableNeedsUpdate()) {
281
                $missingFields = $this->record->findMissingFields();
282
283
                $count = count($missingFields);
284
285
                for ($i = 0; $i < $count; ++$i) {
286
                    $this->record->addProperty($missingFields[$i]);
287
                }
288
289
                self::$logger->debug('<<load');
290
                throw new RecordFoundException('Failed to load object of ID ['.$ID.'], table ['.$this->record->getTableName().'] was out of sync with the database so had to be updated!');
291
            }
292
        }
293
294
        self::$logger->debug('<<load');
295
    }
296
297
    /**
298
     * (non-PHPdoc).
299
     *
300
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllOldVersions()
301
     */
302
    public function loadAllOldVersions($ID)
303
    {
304
        self::$logger->debug('>>loadAllOldVersions(ID=['.$ID.'])');
305
306
        if (!$this->record->getMaintainHistory()) {
307
            throw new RecordFoundException('loadAllOldVersions method called on an active record where no history is maintained!');
308
        }
309
310
        $sqlQuery = 'SELECT version_num FROM '.$this->record->getTableName().'_history WHERE ID = \''.$ID.'\' ORDER BY version_num;';
311
312
        $this->record->setLastQuery($sqlQuery);
313
314
        if (!$result = self::getConnection()->query($sqlQuery)) {
315
            self::$logger->debug('<<loadAllOldVersions');
316
            throw new RecordNotFoundException('Failed to load object versions, SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
317
        }
318
319
        // now build an array of objects to be returned
320
        $objects = array();
321
        $count = 0;
322
        $RecordClass = get_class($this->record);
323
324
        while ($row = $result->fetchArray()) {
325
            try {
326
                $obj = new $RecordClass();
327
                $obj->load($ID, $row['version_num']);
328
                $objects[$count] = $obj;
329
                ++$count;
330
            } catch (ResourceNotAllowedException $e) {
331
                // the resource not allowed will be absent from the list
332
            }
333
        }
334
335
        self::$logger->warn('<<loadAllOldVersions ['.count($objects).']');
336
337
        return $objects;
338
    }
339
340
    /**
341
     * (non-PHPdoc).
342
     *
343
     * @see Alpha\Model\ActiveRecordProviderInterface::loadByAttribute()
344
     */
345
    public function loadByAttribute($attribute, $value, $ignoreClassType = false, $loadAttributes = array())
346
    {
347
        self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
348
            loadAttributes=['.var_export($loadAttributes, true).'])');
349
350
        if (count($loadAttributes) == 0) {
351
            $attributes = $this->record->getPersistentAttributes();
352
        } else {
353
            $attributes = $loadAttributes;
354
        }
355
356
        $fields = '';
357
        foreach ($attributes as $att) {
358
            $fields .= $att.',';
359
        }
360
        $fields = mb_substr($fields, 0, -1);
361
362
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
363
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE '.$attribute.' = :attribute AND classname = :classname LIMIT 1;';
364
        } else {
365
            $sqlQuery = 'SELECT '.$fields.' FROM '.$this->record->getTableName().' WHERE '.$attribute.' = :attribute LIMIT 1;';
366
        }
367
368
        self::$logger->debug('Query=['.$sqlQuery.']');
369
370
        $this->record->setLastQuery($sqlQuery);
371
        $stmt = self::getConnection()->prepare($sqlQuery);
372
373
        if ($stmt instanceof SQLite3Stmt) {
374
            if ($this->record->getPropObject($attribute) instanceof Integer) {
375
                if (!$ignoreClassType && $this->record->isTableOverloaded()) {
376
                    $stmt->bindValue(':attribute', $value, SQLITE3_INTEGER);
377
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
378
                } else {
379
                    $stmt->bindValue(':attribute', $value, SQLITE3_INTEGER);
380
                }
381
            } else {
382
                if (!$ignoreClassType && $this->record->isTableOverloaded()) {
383
                    $stmt->bindValue(':attribute', $value, SQLITE3_TEXT);
384
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
385
                } else {
386
                    $stmt->bindValue(':attribute', $value, SQLITE3_TEXT);
387
                }
388
            }
389
390
            $result = $stmt->execute();
391
392
            // there should only ever be one (or none)
393
            $row = $result->fetchArray(SQLITE3_ASSOC);
394
395
            $stmt->close();
396
        } else {
397
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
398
            if (!$this->record->checkTableExists()) {
399
                $this->record->makeTable();
400
401
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
402
            }
403
404
            return;
405
        }
406
407
        if (!isset($row['ID']) || $row['ID'] < 1) {
408
            self::$logger->debug('<<loadByAttribute');
409
            throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], not found in database.');
410
        }
411
412
        $this->record->setID($row['ID']);
413
414
        // get the class attributes
415
        $reflection = new ReflectionClass(get_class($this->record));
416
        $properties = $reflection->getProperties();
417
418
        try {
419
            foreach ($properties as $propObj) {
420
                $propName = $propObj->name;
421
422
                if (isset($row[$propName])) {
423
                    // filter transient attributes
424
                    if (!in_array($propName, $this->record->getTransientAttributes())) {
425
                        $this->record->set($propName, $row[$propName]);
426
                    } elseif (!$propObj->isPrivate() && $this->record->get($propName) != '' && $this->record->getPropObject($propName) instanceof Relation) {
427
                        $prop = $this->record->getPropObject($propName);
428
429
                        // handle the setting of ONE-TO-MANY relation values
430
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
431
                            $this->record->set($propObj->name, $this->record->getID());
432
                        }
433
                    }
434
                }
435
            }
436
        } catch (IllegalArguementException $e) {
437
            self::$logger->warn('Bad data stored in the table ['.$this->record->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
438
        } catch (PHPException $e) {
439
            // it is possible that the load failed due to the table not being up-to-date
440
            if ($this->record->checkTableNeedsUpdate()) {
441
                $missingFields = $this->record->findMissingFields();
442
443
                $count = count($missingFields);
444
445
                for ($i = 0; $i < $count; ++$i) {
446
                    $this->record->addProperty($missingFields[$i]);
447
                }
448
449
                self::$logger->debug('<<loadByAttribute');
450
                throw new RecordNotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table ['.$this->record->getTableName().'] was out of sync with the database so had to be updated!');
451
            }
452
        }
453
454
        self::$logger->debug('<<loadByAttribute');
455
    }
456
457
    /**
458
     * (non-PHPdoc).
459
     *
460
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAll()
461
     */
462
    public function loadAll($start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
463
    {
464
        self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
465
466
        // ensure that the field name provided in the orderBy param is legit
467
        try {
468
            $this->record->get($orderBy);
469
        } catch (AlphaException $e) {
470
            throw new AlphaException('The field name ['.$orderBy.'] provided in the param orderBy does not exist on the class ['.get_class($this->record).']');
471
        }
472
473
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
474
            if ($limit == 0) {
475
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE classname = \''.get_class($this->record).'\' ORDER BY '.$orderBy.' '.$order.';';
476
            } else {
477
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE classname = \''.get_class($this->record).'\' ORDER BY '.$orderBy.' '.$order.' LIMIT '.
478
                    $limit.' OFFSET '.$start.';';
479
            }
480
        } else {
481
            if ($limit == 0) {
482
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' ORDER BY '.$orderBy.' '.$order.';';
483
            } else {
484
                $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' ORDER BY '.$orderBy.' '.$order.' LIMIT '.$limit.' OFFSET '.$start.';';
485
            }
486
        }
487
488
        $this->record->setLastQuery($sqlQuery);
489
490
        if (!$result = self::getConnection()->query($sqlQuery)) {
491
            self::$logger->debug('<<loadAll');
492
            throw new RecordNotFoundException('Failed to load object IDs, SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
493
        }
494
495
        // now build an array of objects to be returned
496
        $objects = array();
497
        $count = 0;
498
        $RecordClass = get_class($this->record);
499
500
        while ($row = $result->fetchArray()) {
501
            try {
502
                $obj = new $RecordClass();
503
                $obj->load($row['ID']);
504
                $objects[$count] = $obj;
505
                ++$count;
506
            } catch (ResourceNotAllowedException $e) {
507
                // the resource not allowed will be absent from the list
508
            }
509
        }
510
511
        self::$logger->debug('<<loadAll ['.count($objects).']');
512
513
        return $objects;
514
    }
515
516
    /**
517
     * (non-PHPdoc).
518
     *
519
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttribute()
520
     */
521
    public function loadAllByAttribute($attribute, $value, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
522
    {
523
        self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
524
525
        if ($start != 0 && $limit != 0) {
526
            $limit = ' LIMIT '.$limit.' OFFSET '.$start.';';
527
        } else {
528
            $limit = ';';
529
        }
530
531
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
532
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE $attribute = :attribute AND classname = :classname ORDER BY ".$orderBy.' '.$order.$limit;
533
        } else {
534
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE $attribute = :attribute ORDER BY ".$orderBy.' '.$order.$limit;
535
        }
536
537
        $this->record->setLastQuery($sqlQuery);
538
        self::$logger->debug($sqlQuery);
539
540
        $stmt = self::getConnection()->prepare($sqlQuery);
541
542
        $objects = array();
543
544
        if ($stmt instanceof SQLite3Stmt) {
545
            if ($this->record->getPropObject($attribute) instanceof Integer) {
546
                if ($this->record->isTableOverloaded()) {
547
                    $stmt->bindValue(':attribute', $value, SQLITE3_INTEGER);
548
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
549
                } else {
550
                    $stmt->bindValue(':attribute', $value, SQLITE3_INTEGER);
551
                }
552
            } else {
553
                if ($this->record->isTableOverloaded()) {
554
                    $stmt->bindValue(':attribute', $value, SQLITE3_TEXT);
555
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
556
                } else {
557
                    $stmt->bindValue(':attribute', $value, SQLITE3_TEXT);
558
                }
559
            }
560
561
            $result = $stmt->execute();
562
563
            // now build an array of objects to be returned
564
            $count = 0;
565
            $RecordClass = get_class($this->record);
566
567
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
568
                try {
569
                    $argsCount = count($constructorArgs);
570
571
                    if ($argsCount < 1) {
572
                        $obj = new $RecordClass();
573
                    } else {
574
                        switch ($argsCount) {
575
                            case 1:
576
                                $obj = new $RecordClass($constructorArgs[0]);
577
                                break;
578
                            case 2:
579
                                $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
580
                                break;
581
                            case 3:
582
                                $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
583
                                break;
584
                            case 4:
585
                                $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
586
                                break;
587
                            case 5:
588
                                $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
589
                                break;
590
                            default:
591
                                throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
592
                        }
593
                    }
594
595
                    $obj->load($row['ID']);
596
                    $objects[$count] = $obj;
597
                    ++$count;
598
                } catch (ResourceNotAllowedException $e) {
599
                    // the resource not allowed will be absent from the list
600
                }
601
            }
602
603
            $stmt->close();
604
        } else {
605
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
606
607
            if (!$this->record->checkTableExists()) {
608
                $this->record->makeTable();
609
610
                throw new RecordFoundException('Failed to load objects by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
611
            }
612
613
            self::$logger->debug('<<loadAllByAttribute []');
614
615
            return array();
616
        }
617
618
        self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
619
620
        return $objects;
621
    }
622
623
    /**
624
     * (non-PHPdoc).
625
     *
626
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByAttributes()
627
     */
628
    public function loadAllByAttributes($attributes = array(), $values = array(), $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false, $constructorArgs = array())
629
    {
630
        self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
631
            $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
632
633
        $whereClause = ' WHERE';
634
635
        $count = count($attributes);
636
637
        for ($i = 0; $i < $count; ++$i) {
638
            $whereClause .= ' '.$attributes[$i].' = :'.$attributes[$i].' AND';
639
            self::$logger->debug($whereClause);
640
        }
641
642
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
643
            $whereClause .= ' classname = :classname AND';
644
        }
645
646
        // remove the last " AND"
647
        $whereClause = mb_substr($whereClause, 0, -4);
648
649
        if ($limit != 0) {
650
            $limit = ' LIMIT '.$limit.' OFFSET '.$start.';';
651
        } else {
652
            $limit = ';';
653
        }
654
655
        $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().$whereClause.' ORDER BY '.$orderBy.' '.$order.$limit;
656
657
        $this->record->setLastQuery($sqlQuery);
658
659
        $stmt = self::getConnection()->prepare($sqlQuery);
660
661
        if ($stmt instanceof SQLite3Stmt) {
662
            // bind params where required attributes are provided
663
            if (count($attributes) > 0 && count($attributes) == count($values)) {
664
                for ($i = 0; $i < count($attributes); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
665
                    if (strcspn($values[$i], '0123456789') != strlen($values[$i])) {
666
                        $stmt->bindValue(':'.$attributes[$i], $values[$i], SQLITE3_INTEGER);
667
                    } else {
668
                        $stmt->bindValue(':'.$attributes[$i], $values[$i], SQLITE3_TEXT);
669
                    }
670
                }
671
            } else {
672
                // we'll still need to bind the "classname" for overloaded records...
673
                if ($this->record->isTableOverloaded()) {
674
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
675
                }
676
            }
677
678
            $result = $stmt->execute();
679
        } else {
680
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
681
682
            if (!$this->record->checkTableExists()) {
683
                $this->record->makeTable();
684
685
                throw new RecordFoundException('Failed to load objects by attributes ['.var_export($attributes, true).'] and values ['.
686
                    var_export($values, true).'], table did not exist so had to create!');
687
            }
688
689
            self::$logger->debug('<<loadAllByAttributes []');
690
691
            return array();
692
        }
693
694
        // now build an array of objects to be returned
695
        $objects = array();
696
        $count = 0;
697
        $RecordClass = get_class($this->record);
698
699
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
700
            try {
701
                $argsCount = count($constructorArgs);
702
703
                if ($argsCount < 1) {
704
                    $obj = new $RecordClass();
705
                } else {
706
                    switch ($argsCount) {
707
                        case 1:
708
                            $obj = new $RecordClass($constructorArgs[0]);
709
                            break;
710
                        case 2:
711
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1]);
712
                            break;
713
                        case 3:
714
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2]);
715
                            break;
716
                        case 4:
717
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3]);
718
                            break;
719
                        case 5:
720
                            $obj = new $RecordClass($constructorArgs[0], $constructorArgs[1], $constructorArgs[2], $constructorArgs[3], $constructorArgs[4]);
721
                            break;
722
                        default:
723
                            throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
724
                    }
725
                }
726
727
                $obj->load($row['ID']);
728
                $objects[$count] = $obj;
729
                ++$count;
730
            } catch (ResourceNotAllowedException $e) {
731
                // the resource not allowed will be absent from the list
732
            }
733
        }
734
735
        $stmt->close();
736
737
        self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
738
739
        return $objects;
740
    }
741
742
    /**
743
     * (non-PHPdoc).
744
     *
745
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllByDayUpdated()
746
     */
747
    public function loadAllByDayUpdated($date, $start = 0, $limit = 0, $orderBy = 'ID', $order = 'ASC', $ignoreClassType = false)
748
    {
749
        self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
750
751
        if ($start != 0 && $limit != 0) {
752
            $limit = ' LIMIT '.$limit.' OFFSET '.$start.';';
753
        } else {
754
            $limit = ';';
755
        }
756
757
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
758
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' AND classname = '".addslashes(get_class($this->record))."' ORDER BY ".$orderBy.' '.$order.$limit;
759
        } else {
760
            $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' ORDER BY ".$orderBy.' '.$order.$limit;
761
        }
762
763
        $this->record->setLastQuery($sqlQuery);
764
765
        if (!$result = self::getConnection()->query($sqlQuery)) {
766
            self::$logger->debug('<<loadAllByDayUpdated');
767
            throw new RecordNotFoundException('Failed to load object IDs, SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
768
        }
769
770
        // now build an array of objects to be returned
771
        $objects = array();
772
        $count = 0;
773
        $RecordClass = get_class($this->record);
774
775
        while ($row = $result->fetchArray()) {
776
            $obj = new $RecordClass();
777
            $obj->load($row['ID']);
778
            $objects[$count] = $obj;
779
            ++$count;
780
        }
781
782
        self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
783
784
        return $objects;
785
    }
786
787
    /**
788
     * (non-PHPdoc).
789
     *
790
     * @see Alpha\Model\ActiveRecordProviderInterface::loadAllFieldValuesByAttribute()
791
     */
792
    public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order = 'ASC', $ignoreClassType = false)
793
    {
794
        self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
795
796
        if (!$ignoreClassType && $this->record->isTableOverloaded()) {
797
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->record->getTableName()." WHERE $attribute = '$value' AND classname = '".addslashes(get_class($this->record))."' ORDER BY ID ".$order.';';
798
        } else {
799
            $sqlQuery = 'SELECT '.$returnAttribute.' FROM '.$this->record->getTableName()." WHERE $attribute = '$value' ORDER BY ID ".$order.';';
800
        }
801
802
        $this->record->setLastQuery($sqlQuery);
803
804
        self::$logger->debug('lastQuery ['.$sqlQuery.']');
805
806
        if (!$result = self::getConnection()->query($sqlQuery)) {
807
            self::$logger->debug('<<loadAllFieldValuesByAttribute');
808
            throw new RecordNotFoundException('Failed to load field ['.$returnAttribute.'] values, SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
809
        }
810
811
        // now build an array of attribute values to be returned
812
        $values = array();
813
        $count = 0;
814
815
        while ($row = $result->fetchArray()) {
816
            $values[$count] = $row[$returnAttribute];
817
            ++$count;
818
        }
819
820
        self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
821
822
        return $values;
823
    }
824
825
    /**
826
     * (non-PHPdoc).
827
     *
828
     * @see Alpha\Model\ActiveRecordProviderInterface::save()
829
     */
830
    public function save()
831
    {
832
        self::$logger->debug('>>save()');
833
834
        $config = ConfigProvider::getInstance();
835
        $sessionProvider = $config->get('session.provider.name');
836
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
837
838
        // get the class attributes
839
        $reflection = new ReflectionClass(get_class($this->record));
840
        $properties = $reflection->getProperties();
841
842
        if ($this->record->getVersion() != $this->record->getVersionNumber()->getValue()) {
843
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
844
        }
845
846
        // set the "updated by" fields, we can only set the user id if someone is logged in
847
        if ($session->get('currentUser') != null) {
848
            $this->record->set('updated_by', $session->get('currentUser')->getID());
849
        }
850
851
        $this->record->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
852
853
        // check to see if it is a transient object that needs to be inserted
854
        if ($this->record->isTransient()) {
855
            $savedFields = array();
856
            $sqlQuery = 'INSERT INTO '.$this->record->getTableName().' (';
857
858
            foreach ($properties as $propObj) {
859
                $propName = $propObj->name;
860
                if (!in_array($propName, $this->record->getTransientAttributes())) {
861
                    // Skip the ID, database auto number takes care of this.
862
                    if ($propName != 'ID' && $propName != 'version_num') {
863
                        $sqlQuery .= "$propName,";
864
                        $savedFields[] = $propName;
865
                    }
866
867
                    if ($propName == 'version_num') {
868
                        $sqlQuery .= 'version_num,';
869
                        $savedFields[] = 'version_num';
870
                    }
871
                }
872
            }
873
            if ($this->record->isTableOverloaded()) {
874
                $sqlQuery .= 'classname,';
875
            }
876
877
            $sqlQuery = rtrim($sqlQuery, ',');
878
879
            $sqlQuery .= ') VALUES (';
880
881
            foreach ($savedFields as $savedField) {
882
                $sqlQuery .= ':'.$savedField.',';
883
            }
884
885
            if ($this->record->isTableOverloaded()) {
886
                $sqlQuery .= ':classname,';
887
            }
888
889
            $sqlQuery = rtrim($sqlQuery, ',').')';
890
891
            $this->record->setLastQuery($sqlQuery);
892
            self::$logger->debug('Query ['.$sqlQuery.']');
893
894
            $stmt = self::getConnection()->prepare($sqlQuery);
895
896
            if ($stmt instanceof SQLite3Stmt) {
897
                foreach ($savedFields as $savedField) {
898
                    if ($this->record->get($savedField) instanceof Integer) {
899
                        $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_INTEGER);
900
                    } else {
901
                        $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_TEXT);
902
                    }
903
                }
904
905
                if ($this->record->isTableOverloaded()) {
906
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
907
                }
908
909
                $stmt->bindValue(':version_num', 1, SQLITE3_INTEGER); // on an initial save, this will always be 1
910
                $this->record->set('version_num', 1);
911
912
                try {
913
                    $stmt->execute();
914
                } catch (Exception $e) {
915
                    if (self::getConnection()->lastErrorCode() == 19) {
916
                        throw new ValidationException('Unique key violation while trying to save object, exception ['.$e->getMessage().'], SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
917
                    } else {
918
                        throw new FailedSaveException('Failed to save object, exception ['.$e->getMessage().'], DB error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
919
                    }
920
                }
921
            } else {
922
                throw new FailedSaveException('Failed to save object, exception ['.$e->getMessage().'], DB error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
923
            }
924
        } else {
925
            // assume that it is a persistent object that needs to be updated
926
            $savedFields = array();
927
            $sqlQuery = 'UPDATE '.$this->record->getTableName().' SET ';
928
929
            foreach ($properties as $propObj) {
930
                $propName = $propObj->name;
931
                if (!in_array($propName, $this->record->getTransientAttributes())) {
932
                    // Skip the ID, database auto number takes care of this.
933
                    if ($propName != 'ID' && $propName != 'version_num') {
934
                        $sqlQuery .= "$propName = :$propName,";
935
                        $savedFields[] = $propName;
936
                    }
937
938
                    if ($propName == 'version_num') {
939
                        $sqlQuery .= 'version_num = :version_num,';
940
                        $savedFields[] = 'version_num';
941
                    }
942
                }
943
            }
944
945
            if ($this->record->isTableOverloaded()) {
946
                $sqlQuery .= 'classname = :classname,';
947
            }
948
949
            $sqlQuery = rtrim($sqlQuery, ',');
950
951
            $sqlQuery .= ' WHERE ID=:ID;';
952
953
            $this->record->setLastQuery($sqlQuery);
954
            $stmt = self::getConnection()->prepare($sqlQuery);
955
956
            if ($stmt instanceof SQLite3Stmt) {
957
                foreach ($savedFields as $savedField) {
958
                    if ($this->record->get($savedField) instanceof Integer) {
959
                        $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_INTEGER);
960
                    } else {
961
                        $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_TEXT);
962
                    }
963
                }
964
965
                if ($this->record->isTableOverloaded()) {
966
                    $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
967
                }
968
969
                $stmt->bindValue(':ID', $this->record->getID(), SQLITE3_INTEGER);
970
971
                $temp = $this->record->getVersionNumber()->getValue();
972
                $this->record->set('version_num', $temp+1);
973
                $stmt->bindValue(':version_num', $temp+1, SQLITE3_INTEGER);
974
975
                $stmt->execute();
976
            } else {
977
                throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->record->getLastQuery().']');
978
            }
979
        }
980
981
        if ($stmt != null && $stmt != false) {
982
            // populate the updated ID in case we just done an insert
983
            if ($this->record->isTransient()) {
984
                $this->record->setID(self::getConnection()->lastInsertRowID());
985
            }
986
987
            try {
988
                foreach ($properties as $propObj) {
989
                    $propName = $propObj->name;
990
991
                    if ($this->record->getPropObject($propName) instanceof Relation) {
992
                        $prop = $this->record->getPropObject($propName);
993
994
                        // handle the saving of MANY-TO-MANY relation values
995
                        if ($prop->getRelationType() == 'MANY-TO-MANY' && count($prop->getRelatedIDs()) > 0) {
996
                            try {
997
                                try {
998
                                    // check to see if the rel is on this class
999
                                    $side = $prop->getSide(get_class($this->record));
1000
                                } catch (IllegalArguementException $iae) {
1001
                                    $side = $prop->getSide(get_parent_class($this->record));
1002
                                }
1003
1004
                                $lookUp = $prop->getLookup();
1005
1006
                                // first delete all of the old RelationLookup objects for this rel
1007
                                try {
1008
                                    if ($side == 'left') {
1009
                                        $lookUp->deleteAllByAttribute('leftID', $this->record->getID());
1010
                                    } else {
1011
                                        $lookUp->deleteAllByAttribute('rightID', $this->record->getID());
1012
                                    }
1013
                                } catch (Exception $e) {
1014
                                    throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
1015
                                }
1016
1017
                                $IDs = $prop->getRelatedIDs();
1018
1019
                                if (isset($IDs) && !empty($IDs[0])) {
1020
                                    // now for each posted ID, create a new RelationLookup record and save
1021
                                    foreach ($IDs as $id) {
1022
                                        $newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
1023
                                        if ($side == 'left') {
1024
                                            $newLookUp->set('leftID', $this->record->getID());
1025
                                            $newLookUp->set('rightID', $id);
1026
                                        } else {
1027
                                            $newLookUp->set('rightID', $this->record->getID());
1028
                                            $newLookUp->set('leftID', $id);
1029
                                        }
1030
                                        $newLookUp->save();
1031
                                    }
1032
                                }
1033
                            } catch (Exception $e) {
1034
                                throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
1035
                            }
1036
                        }
1037
1038
                        // handle the saving of ONE-TO-MANY relation values
1039
                        if ($prop->getRelationType() == 'ONE-TO-MANY') {
1040
                            $prop->setValue($this->record->getID());
1041
                        }
1042
                    }
1043
                }
1044
            } catch (Exception $e) {
1045
                throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
1046
            }
1047
1048
            $stmt->close();
1049
        } else {
1050
            // there has been an error, so decrement the version number back
1051
            $temp = $this->record->getVersionNumber()->getValue();
1052
            $this->record->set('version_num', $temp-1);
1053
1054
            throw new FailedSaveException('Failed to save object, SQLite error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
1055
        }
1056
1057
        if ($this->record->getMaintainHistory()) {
1058
            $this->record->saveHistory();
1059
        }
1060
    }
1061
1062
    /**
1063
     * (non-PHPdoc).
1064
     *
1065
     * @see Alpha\Model\ActiveRecordProviderInterface::saveAttribute()
1066
     */
1067
    public function saveAttribute($attribute, $value)
1068
    {
1069
        self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
1070
1071
        $config = ConfigProvider::getInstance();
1072
        $sessionProvider = $config->get('session.provider.name');
1073
        $session = ServiceFactory::getInstance($sessionProvider, 'Alpha\Util\Http\Session\SessionProviderInterface');
1074
1075
        if ($this->record->getVersion() != $this->record->getVersionNumber()->getValue()) {
1076
            throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
1077
        }
1078
1079
        // set the "updated by" fields, we can only set the user id if someone is logged in
1080
        if ($session->get('currentUser') != null) {
1081
            $this->record->set('updated_by', $session->get('currentUser')->getID());
1082
        }
1083
1084
        $this->record->set('updated_ts', new Timestamp(date('Y-m-d H:i:s')));
1085
1086
        // assume that it is a persistent object that needs to be updated
1087
        $sqlQuery = 'UPDATE '.$this->record->getTableName().' SET '.$attribute.'=:attribute, version_num=:version, updated_by=:updated_by, updated_ts=:updated_ts WHERE ID=:ID;';
1088
1089
        $this->record->setLastQuery($sqlQuery);
1090
        $stmt = self::getConnection()->prepare($sqlQuery);
1091
1092
        $newVersionNumber = $this->record->getVersionNumber()->getValue()+1;
1093
1094
        if ($stmt instanceof SQLite3Stmt) {
1095
            if ($this->record->getPropObject($attribute) instanceof Integer) {
1096
                $stmt->bindValue(':attribute', $value, SQLITE3_INTEGER);
1097
            } else {
1098
                $stmt->bindValue(':attribute', $value, SQLITE3_TEXT);
1099
            }
1100
1101
            $updatedBy = $this->record->get('updated_by');
1102
            $updatedTS = $this->record->get('updated_ts');
1103
1104
            $stmt->bindValue(':version', $newVersionNumber, SQLITE3_INTEGER);
1105
            $stmt->bindValue(':updated_by', $updatedBy, SQLITE3_INTEGER);
1106
            $stmt->bindValue(':updated_ts', $updatedTS, SQLITE3_TEXT);
1107
            $stmt->bindValue(':ID', $this->record->getID(), SQLITE3_INTEGER);
1108
1109
            $stmt->execute();
1110
        } else {
1111
            throw new FailedSaveException('Failed to save attribute, error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
1112
        }
1113
1114
        $stmt->close();
1115
1116
        $this->record->set($attribute, $value);
1117
        $this->record->set('version_num', $newVersionNumber);
1118
1119
        if ($this->record->getMaintainHistory()) {
1120
            $this->record->saveHistory();
1121
        }
1122
1123
        self::$logger->debug('<<saveAttribute');
1124
    }
1125
1126
    /**
1127
     * (non-PHPdoc).
1128
     *
1129
     * @see Alpha\Model\ActiveRecordProviderInterface::saveHistory()
1130
     */
1131
    public function saveHistory()
1132
    {
1133
        self::$logger->debug('>>saveHistory()');
1134
1135
        // get the class attributes
1136
        $reflection = new ReflectionClass(get_class($this->record));
1137
        $properties = $reflection->getProperties();
1138
1139
        $savedFields = array();
1140
        $attributeNames = array();
1141
        $attributeValues = array();
1142
1143
        $sqlQuery = 'INSERT INTO '.$this->record->getTableName().'_history (';
1144
1145
        foreach ($properties as $propObj) {
1146
            $propName = $propObj->name;
1147
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1148
                $sqlQuery .= "$propName,";
1149
                $attributeNames[] = $propName;
1150
                $attributeValues[] = $this->record->get($propName);
1151
                $savedFields[] = $propName;
1152
            }
1153
        }
1154
1155
        if ($this->record->isTableOverloaded()) {
1156
            $sqlQuery .= 'classname,';
1157
        }
1158
1159
        $sqlQuery = rtrim($sqlQuery, ',');
1160
1161
        $sqlQuery .= ') VALUES (';
1162
1163
        foreach ($savedFields as $savedField) {
1164
            $sqlQuery .= ':'.$savedField.',';
1165
        }
1166
1167
        if ($this->record->isTableOverloaded()) {
1168
            $sqlQuery .= ':classname,';
1169
        }
1170
1171
        $sqlQuery = rtrim($sqlQuery, ',').')';
1172
1173
        $this->record->setLastQuery($sqlQuery);
1174
        self::$logger->debug('Query ['.$sqlQuery.']');
1175
1176
        $stmt = self::getConnection()->prepare($sqlQuery);
1177
1178
        if ($stmt instanceof SQLite3Stmt) {
1179
            foreach ($savedFields as $savedField) {
1180
                if ($this->record->get($savedField) instanceof Integer) {
1181
                    $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_INTEGER);
1182
                } else {
1183
                    $stmt->bindValue(':'.$savedField, $this->record->get($savedField), SQLITE3_TEXT);
1184
                }
1185
            }
1186
1187
            if ($this->record->isTableOverloaded()) {
1188
                $stmt->bindValue(':classname', get_class($this->record), SQLITE3_TEXT);
1189
            }
1190
1191
            $stmt->execute();
1192
        } else {
1193
            throw new FailedSaveException('Failed to save object history, error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
1194
        }
1195
    }
1196
1197
    /**
1198
     * (non-PHPdoc).
1199
     *
1200
     * @see Alpha\Model\ActiveRecordProviderInterface::delete()
1201
     */
1202
    public function delete()
1203
    {
1204
        self::$logger->debug('>>delete()');
1205
1206
        $sqlQuery = 'DELETE FROM '.$this->record->getTableName().' WHERE ID = :ID;';
1207
1208
        $this->record->setLastQuery($sqlQuery);
1209
1210
        $stmt = self::getConnection()->prepare($sqlQuery);
1211
1212
        if ($stmt instanceof SQLite3Stmt) {
1213
            $stmt->bindValue(':ID', $this->record->getID(), SQLITE3_INTEGER);
1214
            $stmt->execute();
1215
            self::$logger->debug('Deleted the object ['.$this->record->getID().'] of class ['.get_class($this->record).']');
1216
        } else {
1217
            throw new FailedDeleteException('Failed to delete object ['.$this->record->getID().'], error is ['.self::getLastDatabaseError().'], query ['.$this->record->getLastQuery().']');
1218
        }
1219
1220
        $stmt->close();
1221
1222
        self::$logger->debug('<<delete');
1223
    }
1224
1225
    /**
1226
     * (non-PHPdoc).
1227
     *
1228
     * @see Alpha\Model\ActiveRecordProviderInterface::getVersion()
1229
     */
1230
    public function getVersion()
1231
    {
1232
        self::$logger->debug('>>getVersion()');
1233
1234
        $sqlQuery = 'SELECT version_num FROM '.$this->record->getTableName().' WHERE ID = :ID;';
1235
        $this->record->setLastQuery($sqlQuery);
1236
1237
        $stmt = self::getConnection()->prepare($sqlQuery);
1238
1239
        if ($stmt instanceof SQLite3Stmt) {
1240
            $stmt->bindValue(':ID', $this->record->getID(), SQLITE3_INTEGER);
1241
1242
            $result = $stmt->execute();
1243
1244
            // there should only ever be one (or none)
1245
            $row = $result->fetchArray(SQLITE3_ASSOC);
1246
1247
            $stmt->close();
1248
        } else {
1249
            self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
1250
            if (!$this->record->checkTableExists()) {
1251
                $this->record->makeTable();
1252
1253
                throw new RecordNotFoundException('Failed to get the version number, table did not exist so had to create!');
1254
            }
1255
1256
            return;
1257
        }
1258
1259
        if (!isset($row['version_num']) || $row['version_num'] < 1) {
1260
            self::$logger->debug('<<getVersion [0]');
1261
1262
            return 0;
1263
        } else {
1264
            $version_num = $row['version_num'];
1265
1266
            self::$logger->debug('<<getVersion ['.$version_num.']');
1267
1268
            return $version_num;
1269
        }
1270
    }
1271
1272
    /**
1273
     * (non-PHPdoc).
1274
     *
1275
     * @see Alpha\Model\ActiveRecordProviderInterface::makeTable()
1276
     */
1277
    public function makeTable($checkIndexes = true)
1278
    {
1279
        self::$logger->debug('>>makeTable()');
1280
1281
        $sqlQuery = 'CREATE TABLE '.$this->record->getTableName().' (ID INTEGER PRIMARY KEY,';
1282
1283
        // get the class attributes
1284
        $reflection = new ReflectionClass(get_class($this->record));
1285
        $properties = $reflection->getProperties();
1286
1287
        $foreignKeys = array();
1288
1289
        foreach ($properties as $propObj) {
1290
            $propName = $propObj->name;
1291
1292
            if (!in_array($propName, $this->record->getTransientAttributes()) && $propName != 'ID') {
1293
                $prop = $this->record->getPropObject($propName);
1294
1295
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1296
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().') NOT NULL,';
1297
                } elseif ($prop instanceof Integer) {
1298
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().'),';
1299
                } elseif ($prop instanceof Double) {
1300
                    $sqlQuery .= "$propName REAL(".$prop->getSize(true).'),';
1301
                } elseif ($prop instanceof SmallText) {
1302
                    $sqlQuery .= "$propName TEXT(".$prop->getSize().'),';
1303
                } elseif ($prop instanceof Text) {
1304
                    $sqlQuery .= "$propName TEXT,";
1305
                } elseif ($prop instanceof Boolean) {
1306
                    $sqlQuery .= "$propName INTEGER(1) DEFAULT '0',";
1307
                } elseif ($prop instanceof Date) {
1308
                    $sqlQuery .= "$propName TEXT,";
1309
                } elseif ($prop instanceof Timestamp) {
1310
                    $sqlQuery .= "$propName TEXT,";
1311
                } elseif ($prop instanceof Enum) {
1312
                    $sqlQuery .= "$propName TEXT,";
1313
                } elseif ($prop instanceof DEnum) {
1314
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1315
                    $denum->saveIfNew();
1316
                    $sqlQuery .= "$propName INTEGER(11),";
1317
                } elseif ($prop instanceof Relation) {
1318
                    $sqlQuery .= "$propName INTEGER(11),";
1319
1320
                    $rel = $this->record->getPropObject($propName);
1321
1322
                    $relatedField = $rel->getRelatedClassField();
1323
                    $relatedClass = $rel->getRelatedClass();
1324
                    $relatedRecord = new $relatedClass();
1325
                    $tableName = $relatedRecord->getTableName();
1326
                    $foreignKeys[$propName] = array($tableName, $relatedField);
1327
                } else {
1328
                    $sqlQuery .= '';
1329
                }
1330
            }
1331
        }
1332
1333
        if ($this->record->isTableOverloaded()) {
1334
            $sqlQuery .= 'classname TEXT(100)';
1335
        } else {
1336
            $sqlQuery = mb_substr($sqlQuery, 0, -1);
1337
        }
1338
1339
        if (count($foreignKeys) > 0) {
1340
            foreach ($foreignKeys as $field => $related) {
1341
                $sqlQuery .= ', FOREIGN KEY ('.$field.') REFERENCES '.$related[0].'('.$related[1].')';
1342
            }
1343
        }
1344
1345
        if (count($this->foreignKeys) > 0) {
1346
            foreach ($this->foreignKeys as $field => $related) {
1347
                $sqlQuery .= ', FOREIGN KEY ('.$field.') REFERENCES '.$related[0].'('.$related[1].')';
1348
            }
1349
        }
1350
1351
        $sqlQuery .= ');';
1352
1353
        $this->record->setLastQuery($sqlQuery);
1354
1355
        if (!self::getConnection()->exec($sqlQuery)) {
1356
            self::$logger->debug('<<makeTable');
1357
            throw new AlphaException('Failed to create the table ['.$this->record->getTableName().'] for the class ['.get_class($this->record).'], database error is ['.self::getLastDatabaseError().']');
1358
        }
1359
1360
        // check the table indexes if any additional ones required
1361
        if ($checkIndexes) {
1362
            $this->checkIndexes();
1363
        }
1364
1365
        if ($this->record->getMaintainHistory()) {
1366
            $this->record->makeHistoryTable();
1367
        }
1368
1369
        self::$logger->debug('<<makeTable');
1370
    }
1371
1372
    /**
1373
     * (non-PHPdoc).
1374
     *
1375
     * @see Alpha\Model\ActiveRecordProviderInterface::makeHistoryTable()
1376
     */
1377
    public function makeHistoryTable()
1378
    {
1379
        self::$logger->debug('>>makeHistoryTable()');
1380
1381
        $sqlQuery = 'CREATE TABLE '.$this->record->getTableName().'_history (ID INTEGER NOT NULL,';
1382
1383
        // get the class attributes
1384
        $reflection = new ReflectionClass(get_class($this->record));
1385
        $properties = $reflection->getProperties();
1386
1387
        foreach ($properties as $propObj) {
1388
            $propName = $propObj->name;
1389
1390
            if (!in_array($propName, $this->record->getTransientAttributes()) && $propName != 'ID') {
1391
                $prop = $this->record->getPropObject($propName);
1392
1393
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1394
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().') NOT NULL,';
1395
                } elseif ($prop instanceof Integer) {
1396
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().'),';
1397
                } elseif ($prop instanceof Double) {
1398
                    $sqlQuery .= "$propName REAL(".$prop->getSize(true).'),';
1399
                } elseif ($prop instanceof SmallText) {
1400
                    $sqlQuery .= "$propName TEXT(".$prop->getSize().'),';
1401
                } elseif ($prop instanceof Text) {
1402
                    $sqlQuery .= "$propName TEXT,";
1403
                } elseif ($prop instanceof Boolean) {
1404
                    $sqlQuery .= "$propName INTEGER(1) DEFAULT '0',";
1405
                } elseif ($prop instanceof Date) {
1406
                    $sqlQuery .= "$propName TEXT,";
1407
                } elseif ($prop instanceof Timestamp) {
1408
                    $sqlQuery .= "$propName TEXT,";
1409
                } elseif ($prop instanceof Enum) {
1410
                    $sqlQuery .= "$propName TEXT,";
1411
                } elseif ($prop instanceof DEnum) {
1412
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1413
                    $denum->saveIfNew();
1414
                    $sqlQuery .= "$propName INTEGER(11),";
1415
                } elseif ($prop instanceof Relation) {
1416
                    $sqlQuery .= "$propName INTEGER(11),";
1417
                } else {
1418
                    $sqlQuery .= '';
1419
                }
1420
            }
1421
        }
1422
1423
        if ($this->record->isTableOverloaded()) {
1424
            $sqlQuery .= 'classname TEXT(100),';
1425
        }
1426
1427
        $sqlQuery .= 'PRIMARY KEY (ID, version_num));';
1428
1429
        $this->record->setLastQuery($sqlQuery);
1430
1431
        if (!$result = self::getConnection()->query($sqlQuery)) {
1432
            self::$logger->debug('<<makeHistoryTable');
1433
            throw new AlphaException('Failed to create the table ['.$this->record->getTableName().'_history] for the class ['.get_class($this->record).'], database error is ['.self::getLastDatabaseError().']');
1434
        }
1435
1436
        self::$logger->debug('<<makeHistoryTable');
1437
    }
1438
1439
    /**
1440
     * (non-PHPdoc).
1441
     *
1442
     * @see Alpha\Model\ActiveRecordProviderInterface::rebuildTable()
1443
     */
1444
    public function rebuildTable()
1445
    {
1446
        self::$logger->debug('>>rebuildTable()');
1447
1448
        // the use of "IF EXISTS" here requires SQLite 3.3.0 or above.
1449
        $sqlQuery = 'DROP TABLE IF EXISTS '.$this->record->getTableName().';';
1450
1451
        $this->record->setLastQuery($sqlQuery);
1452
1453
        if (!$result = self::getConnection()->query($sqlQuery)) {
1454
            self::$logger->debug('<<rebuildTable');
1455
            throw new AlphaException('Failed to drop the table ['.$this->record->getTableName().'] for the class ['.get_class($this->record).'], database error is ['.self::getLastDatabaseError().']');
1456
        }
1457
1458
        $this->record->makeTable();
1459
1460
        self::$logger->debug('<<rebuildTable');
1461
    }
1462
1463
    /**
1464
     * (non-PHPdoc).
1465
     *
1466
     * @see Alpha\Model\ActiveRecordProviderInterface::dropTable()
1467
     */
1468
    public function dropTable($tableName = null)
1469
    {
1470
        self::$logger->debug('>>dropTable()');
1471
1472
        if ($tableName === null) {
1473
            $tableName = $this->record->getTableName();
1474
        }
1475
1476
        // the use of "IF EXISTS" here requires SQLite 3.3.0 or above.
1477
        $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.';';
1478
1479
        $this->record->setLastQuery($sqlQuery);
1480
1481
        if (!$result = self::getConnection()->query($sqlQuery)) {
1482
            self::$logger->debug('<<dropTable');
1483
            throw new AlphaException('Failed to drop the table ['.$tableName.'] for the class ['.get_class($this->record).'], query is ['.$this->record->getLastQuery().']');
1484
        }
1485
1486
        if ($this->record->getMaintainHistory()) {
1487
            $sqlQuery = 'DROP TABLE IF EXISTS '.$tableName.'_history;';
1488
1489
            $this->record->setLastQuery($sqlQuery);
1490
1491
            if (!$result = self::getConnection()->query($sqlQuery)) {
1492
                self::$logger->debug('<<dropTable');
1493
                throw new AlphaException('Failed to drop the table ['.$tableName.'_history] for the class ['.get_class($this->record).'], query is ['.$this->record->getLastQuery().']');
1494
            }
1495
        }
1496
1497
        self::$logger->debug('<<dropTable');
1498
    }
1499
1500
    /**
1501
     * (non-PHPdoc).
1502
     *
1503
     * @see Alpha\Model\ActiveRecordProviderInterface::addProperty()
1504
     */
1505
    public function addProperty($propName)
1506
    {
1507
        self::$logger->debug('>>addProperty(propName=['.$propName.'])');
1508
1509
        $sqlQuery = 'ALTER TABLE '.$this->record->getTableName().' ADD ';
1510
1511
        if ($this->isTableOverloaded() && $propName == 'classname') {
1512
            $sqlQuery .= 'classname TEXT(100)';
1513
        } else {
1514
            if (!in_array($propName, $this->record->getDefaultAttributes()) && !in_array($propName, $this->record->getTransientAttributes())) {
1515
                $prop = $this->record->getPropObject($propName);
1516
1517
                if ($prop instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID')) {
1518
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().') NOT NULL';
1519
                } elseif ($prop instanceof Integer) {
1520
                    $sqlQuery .= "$propName INTEGER(".$prop->getSize().')';
1521
                } elseif ($prop instanceof Double) {
1522
                    $sqlQuery .= "$propName REAL(".$prop->getSize(true).')';
1523
                } elseif ($prop instanceof SmallText) {
1524
                    $sqlQuery .= "$propName TEXT(".$prop->getSize().')';
1525
                } elseif ($prop instanceof Text) {
1526
                    $sqlQuery .= "$propName TEXT";
1527
                } elseif ($prop instanceof Boolean) {
1528
                    $sqlQuery .= "$propName INTEGER(1) DEFAULT '0'";
1529
                } elseif ($prop instanceof Date) {
1530
                    $sqlQuery .= "$propName TEXT";
1531
                } elseif ($prop instanceof Timestamp) {
1532
                    $sqlQuery .= "$propName TEXT";
1533
                } elseif ($prop instanceof Enum) {
1534
                    $sqlQuery .= "$propName TEXT";
1535
                } elseif ($prop instanceof DEnum) {
1536
                    $denum = new DEnum(get_class($this->record).'::'.$propName);
1537
                    $denum->saveIfNew();
1538
                    $sqlQuery .= "$propName INTEGER(11)";
1539
                } elseif ($prop instanceof Relation) {
1540
                    $sqlQuery .= "$propName INTEGER(11)";
1541
                } else {
1542
                    $sqlQuery .= '';
1543
                }
1544
            }
1545
        }
1546
1547
        $this->record->setLastQuery($sqlQuery);
1548
1549
        if (!$result = self::getConnection()->query($sqlQuery)) {
1550
            self::$logger->debug('<<addProperty');
1551
            throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1552
        } else {
1553
            self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->record->getTableName().'] table for the class ['.get_class($this->record).']');
1554
        }
1555
1556
        if ($this->record->getMaintainHistory()) {
1557
            $sqlQuery = str_replace($this->record->getTableName(), $this->record->getTableName().'_history', $sqlQuery);
1558
1559
            if (!$result = self::getConnection()->query($sqlQuery)) {
1560
                self::$logger->debug('<<addProperty');
1561
                throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->record->getTableName().'_history], query is ['.$this->record->getLastQuery().']');
1562
            } else {
1563
                self::$logger->info('Successfully added the ['.$propName.'] column onto the ['.$this->record->getTableName().'_history] table for the class ['.get_class($this->record).']');
1564
            }
1565
        }
1566
1567
        self::$logger->debug('<<addProperty');
1568
    }
1569
1570
    /**
1571
     * (non-PHPdoc).
1572
     *
1573
     * @see Alpha\Model\ActiveRecordProviderInterface::getMAX()
1574
     */
1575
    public function getMAX()
1576
    {
1577
        self::$logger->debug('>>getMAX()');
1578
1579
        $sqlQuery = 'SELECT MAX(ID) AS max_ID FROM '.$this->record->getTableName();
1580
1581
        $this->record->setLastQuery($sqlQuery);
1582
1583
        try {
1584
            $result = $this->record->query($sqlQuery);
1585
1586
            $row = $result[0];
1587
1588
            if (isset($row['max_ID'])) {
1589
                self::$logger->debug('<<getMAX ['.$row['max_ID'].']');
1590
1591
                return $row['max_ID'];
1592
            } else {
1593
                throw new AlphaException('Failed to get the MAX ID for the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1594
            }
1595
        } catch (Exception $e) {
1596
            self::$logger->debug('<<getMAX');
1597
            throw new AlphaException($e->getMessage());
1598
        }
1599
    }
1600
1601
    /**
1602
     * (non-PHPdoc).
1603
     *
1604
     * @see Alpha\Model\ActiveRecordProviderInterface::getCount()
1605
     */
1606
    public function getCount($attributes = array(), $values = array())
1607
    {
1608
        self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
1609
1610
        if ($this->record->isTableOverloaded()) {
1611
            $whereClause = ' WHERE classname = \''.get_class($this->record).'\' AND';
1612
        } else {
1613
            $whereClause = ' WHERE';
1614
        }
1615
1616
        $count = count($attributes);
1617
1618
        for ($i = 0; $i < $count; ++$i) {
1619
            $whereClause .= ' '.$attributes[$i].' = \''.$values[$i].'\' AND';
1620
            self::$logger->debug($whereClause);
1621
        }
1622
        // remove the last " AND"
1623
        $whereClause = mb_substr($whereClause, 0, -4);
1624
1625
        if ($whereClause != ' WHERE') {
1626
            $sqlQuery = 'SELECT COUNT(ID) AS class_count FROM '.$this->record->getTableName().$whereClause;
1627
        } else {
1628
            $sqlQuery = 'SELECT COUNT(ID) AS class_count FROM '.$this->record->getTableName();
1629
        }
1630
1631
        $this->record->setLastQuery($sqlQuery);
1632
1633
        if (!$result = self::getConnection()->query($sqlQuery)) {
1634
            self::$logger->debug('<<getCount');
1635
            throw new AlphaException('Failed to get the count for the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
1636
        } else {
1637
            $row = $result->fetchArray(SQLITE3_ASSOC);
1638
1639
            self::$logger->debug('<<getCount ['.$row['class_count'].']');
1640
1641
            return $row['class_count'];
1642
        }
1643
    }
1644
1645
    /**
1646
     * (non-PHPdoc).
1647
     *
1648
     * @see Alpha\Model\ActiveRecordProviderInterface::getHistoryCount()
1649
     */
1650
    public function getHistoryCount()
1651
    {
1652
        self::$logger->debug('>>getHistoryCount()');
1653
1654
        if (!$this->record->getMaintainHistory()) {
1655
            throw new AlphaException('getHistoryCount method called on a DAO where no history is maintained!');
1656
        }
1657
1658
        $sqlQuery = 'SELECT COUNT(ID) AS object_count FROM '.$this->record->getTableName().'_history WHERE ID='.$this->record->getID();
1659
1660
        $this->record->setLastQuery($sqlQuery);
1661
        self::$logger->debug('query ['.$sqlQuery.']');
1662
1663
        if (!$result = self::getConnection()->query($sqlQuery)) {
1664
            self::$logger->debug('<<getHistoryCount');
1665
            throw new AlphaException('Failed to get the history count for the business object ['.$this->record->getID().'] from the table ['.$this->record->getTableName().'_history], query is ['.$this->record->getLastQuery().']');
1666
        } else {
1667
            $row = $result->fetchArray(SQLITE3_ASSOC);
1668
1669
            self::$logger->debug('<<getHistoryCount ['.$row['object_count'].']');
1670
1671
            return $row['object_count'];
1672
        }
1673
    }
1674
1675
    /**
1676
     * Given that Enum values are not saved in the database for SQLite, an implementation is not required here.
1677
     *
1678
     * (non-PHPdoc)
1679
     *
1680
     * @see Alpha\Model\ActiveRecordProviderInterface::setEnumOptions()
1681
     *
1682
     * @throws \Alpha\Exception\NotImplementedException
1683
     */
1684
    public function setEnumOptions()
1685
    {
1686
        throw new NotImplementedException('ActiveRecordProviderInterface::setEnumOptions() not implemented by the SQLite3 provider');
1687
    }
1688
1689
    /**
1690
     * (non-PHPdoc).
1691
     *
1692
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableExists()
1693
     */
1694
    public function checkTableExists($checkHistoryTable = false)
1695
    {
1696
        self::$logger->debug('>>checkTableExists(checkHistoryTable=['.$checkHistoryTable.'])');
1697
1698
        $tableExists = false;
1699
1700
        $sqlQuery = 'SELECT name FROM sqlite_master WHERE type = "table";';
1701
        $this->record->setLastQuery($sqlQuery);
1702
1703
        $result = self::getConnection()->query($sqlQuery);
1704
1705
        $tableName = ($checkHistoryTable ? $this->record->getTableName().'_history' : $this->record->getTableName());
1706
1707
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1708
            if (strtolower($row['name']) == mb_strtolower($tableName)) {
1709
                $tableExists = true;
1710
            }
1711
        }
1712
1713
        if ($result) {
1714
            self::$logger->debug('<<checkTableExists ['.$tableExists.']');
1715
1716
            return $tableExists;
1717
        } else {
1718
            self::$logger->debug('<<checkTableExists');
1719
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getLastDatabaseError().']');
1720
        }
1721
    }
1722
1723
    /**
1724
     * (non-PHPdoc).
1725
     *
1726
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordTableExists()
1727
     */
1728
    public static function checkRecordTableExists($RecordClassName, $checkHistoryTable = false)
1729
    {
1730
        if (self::$logger == null) {
1731
            self::$logger = new Logger('ActiveRecordProviderSQLite');
1732
        }
1733
        self::$logger->debug('>>checkRecordTableExists(RecordClassName=['.$RecordClassName.'], checkHistoryTable=['.$checkHistoryTable.'])');
1734
1735
        if (!class_exists($RecordClassName)) {
1736
            throw new IllegalArguementException('The classname provided ['.$checkHistoryTable.'] is not defined!');
1737
        }
1738
1739
        $tableName = $RecordClassName::TABLE_NAME;
1740
1741
        if (empty($tableName)) {
1742
            $tableName = mb_substr($RecordClassName, 0, mb_strpos($RecordClassName, '_'));
1743
        }
1744
1745
        if ($checkHistoryTable) {
1746
            $tableName .= '_history';
1747
        }
1748
1749
        $tableExists = false;
1750
1751
        $sqlQuery = 'SELECT name FROM sqlite_master WHERE type = "table";';
1752
1753
        $result = self::getConnection()->query($sqlQuery);
1754
1755
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1756
            if ($row['name'] == $tableName) {
1757
                $tableExists = true;
1758
            }
1759
        }
1760
1761
        if ($result) {
1762
            self::$logger->debug('<<checkRecordTableExists ['.($tableExists ? 'true' : 'false').']');
1763
1764
            return $tableExists;
1765
        } else {
1766
            self::$logger->debug('<<checkRecordTableExists');
1767
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getLastDatabaseError().']');
1768
        }
1769
    }
1770
1771
    /**
1772
     * (non-PHPdoc).
1773
     *
1774
     * @see Alpha\Model\ActiveRecordProviderInterface::checkTableNeedsUpdate()
1775
     */
1776
    public function checkTableNeedsUpdate()
1777
    {
1778
        self::$logger->debug('>>checkTableNeedsUpdate()');
1779
1780
        if (!$this->record->checkTableExists()) {
1781
            return false;
1782
        }
1783
1784
        $updateRequired = false;
1785
1786
        $matchCount = 0;
1787
1788
        $query = 'PRAGMA table_info('.$this->record->getTableName().')';
1789
        $result = self::getConnection()->query($query);
1790
        $this->record->setLastQuery($query);
1791
1792
        // get the class attributes
1793
        $reflection = new ReflectionClass(get_class($this->record));
1794
        $properties = $reflection->getProperties();
1795
1796
        foreach ($properties as $propObj) {
1797
            $propName = $propObj->name;
1798
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1799
                $foundMatch = false;
1800
1801
                while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1802
                    if ($propName == $row['name']) {
1803
                        $foundMatch = true;
1804
                        break;
1805
                    }
1806
                }
1807
1808
                if (!$foundMatch) {
1809
                    --$matchCount;
1810
                }
1811
1812
                $result->reset();
1813
            }
1814
        }
1815
1816
        // check for the "classname" field in overloaded tables
1817
        if ($this->record->isTableOverloaded()) {
1818
            $foundMatch = false;
1819
1820
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1821
                if ('classname' == $row['name']) {
1822
                    $foundMatch = true;
1823
                    break;
1824
                }
1825
            }
1826
            if (!$foundMatch) {
1827
                --$matchCount;
1828
            }
1829
        }
1830
1831
        if ($matchCount != 0) {
1832
            $updateRequired = true;
1833
        }
1834
1835
        if (!$result) {
1836
            self::$logger->debug('<<checkTableNeedsUpdate');
1837
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getLastDatabaseError().']');
1838
        } else {
1839
            // check the table indexes
1840
            try {
1841
                $this->checkIndexes();
1842
            } catch (AlphaException $ae) {
1843
                self::$logger->warn("Error while checking database indexes:\n\n".$ae->getMessage());
1844
            }
1845
1846
            self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
1847
1848
            return $updateRequired;
1849
        }
1850
    }
1851
1852
    /**
1853
     * (non-PHPdoc).
1854
     *
1855
     * @see Alpha\Model\ActiveRecordProviderInterface::findMissingFields()
1856
     */
1857
    public function findMissingFields()
1858
    {
1859
        self::$logger->debug('>>findMissingFields()');
1860
1861
        $missingFields = array();
1862
        $matchCount = 0;
1863
1864
        $sqlQuery = 'PRAGMA table_info('.$this->record->getTableName().')';
1865
        $result = self::getConnection()->query($sqlQuery);
1866
        $this->record->setLastQuery($sqlQuery);
1867
1868
        // get the class attributes
1869
        $reflection = new ReflectionClass(get_class($this->record));
1870
        $properties = $reflection->getProperties();
1871
1872
        foreach ($properties as $propObj) {
1873
            $propName = $propObj->name;
1874
            if (!in_array($propName, $this->record->getTransientAttributes())) {
1875
                while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1876
                    if ($propName == $row['name']) {
1877
                        ++$matchCount;
1878
                        break;
1879
                    }
1880
                }
1881
                $result->reset();
1882
            } else {
1883
                ++$matchCount;
1884
            }
1885
1886
            if ($matchCount == 0) {
1887
                array_push($missingFields, $propName);
1888
            } else {
1889
                $matchCount = 0;
1890
            }
1891
        }
1892
1893
        // check for the "classname" field in overloaded tables
1894
        if ($this->record->isTableOverloaded()) {
1895
            $foundMatch = false;
1896
1897
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1898
                if ('classname' == $row['name']) {
1899
                    $foundMatch = true;
1900
                    break;
1901
                }
1902
            }
1903
            if (!$foundMatch) {
1904
                array_push($missingFields, 'classname');
1905
            }
1906
        }
1907
1908
        if (!$result) {
1909
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getLastDatabaseError().']');
1910
        }
1911
1912
        self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
1913
1914
        return $missingFields;
1915
    }
1916
1917
    /**
1918
     * (non-PHPdoc).
1919
     *
1920
     * @see Alpha\Model\ActiveRecordProviderInterface::getIndexes()
1921
     */
1922
    public function getIndexes()
1923
    {
1924
        self::$logger->debug('>>getIndexes()');
1925
1926
        $sqlQuery = "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='".$this->record->getTableName()."'";
1927
1928
        $this->record->setLastQuery($sqlQuery);
1929
1930
        $indexNames = array();
1931
1932
        if (!$result = self::getConnection()->query($sqlQuery)) {
1933
            throw new AlphaException('Failed to access the system database correctly, error is ['.self::getLastDatabaseError().']');
1934
        } else {
1935
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1936
                array_push($indexNames, $row['name']);
1937
            }
1938
        }
1939
1940
        // in SQLite foreign keys are not stored in sqlite_master, so we have to run a different query and append the results
1941
        $sqlQuery = 'PRAGMA foreign_key_list('.$this->record->getTableName().')';
1942
        
1943
        $this->record->setLastQuery($sqlQuery);
1944
1945
        if (!$result = self::getConnection()->query($sqlQuery)) {
1946
            self::$logger->warn('Error during pragma table foreign key lookup ['.self::getLastDatabaseError().']');
1947
        } else {
1948
            while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
1949
                // SQLite does not name FK indexes, so we will return a fake name based the same convention used in MySQL
1950
                $fakeIndexName = $this->record->getTableName().'_'.$row['from'].'_fk_idx';
1951
                array_push($indexNames, $fakeIndexName);
1952
            }
1953
        }
1954
1955
        self::$logger->debug('<<getIndexes');
1956
1957
        return $indexNames;
1958
    }
1959
1960
    /**
1961
     * Checks to see if all of the indexes are in place for the record's table, creates those that are missing.
1962
     *
1963
     * @since 1.2
1964
     */
1965
    private function checkIndexes()
1966
    {
1967
        self::$logger->debug('>>checkIndexes()');
1968
1969
        $indexNames = $this->record->getIndexes();
1970
1971
        // process unique keys
1972
        foreach ($this->record->getUniqueAttributes() as $prop) {
1973
            // check for composite indexes
1974
            if (mb_strpos($prop, '+')) {
1975
                $attributes = explode('+', $prop);
1976
1977
                $index_exists = false;
1978
                foreach ($indexNames as $index) {
1979
                    if ($attributes[0].'_'.$attributes[1].'_unq_idx' == $index) {
1980
                        $index_exists = true;
1981
                    }
1982
                    if (count($attributes) == 3) {
1983
                        if ($attributes[0].'_'.$attributes[1].'_'.$attributes[2].'_unq_idx' == $index) {
1984
                            $index_exists = true;
1985
                        }
1986
                    }
1987
                }
1988
1989
                if (!$index_exists) {
1990
                    if (count($attributes) == 3) {
1991
                        $this->record->createUniqueIndex($attributes[0], $attributes[1], $attributes[2]);
1992
                    } else {
1993
                        $this->record->createUniqueIndex($attributes[0], $attributes[1]);
1994
                    }
1995
                }
1996
            } else {
1997
                $index_exists = false;
1998
                foreach ($indexNames as $index) {
1999
                    if ($prop.'_unq_idx' == $index) {
2000
                        $index_exists = true;
2001
                    }
2002
                }
2003
2004
                if (!$index_exists) {
2005
                    $this->createUniqueIndex($prop);
2006
                }
2007
            }
2008
        }
2009
2010
        // process foreign-key indexes
2011
        // get the class attributes
2012
        $reflection = new ReflectionClass(get_class($this->record));
2013
        $properties = $reflection->getProperties();
2014
        foreach ($properties as $propObj) {
2015
            $propName = $propObj->name;
2016
            $prop = $this->record->getPropObject($propName);
2017
            if ($prop instanceof Relation) {
2018
                if ($prop->getRelationType() == 'MANY-TO-ONE') {
2019
                    $indexExists = false;
2020
                    foreach ($indexNames as $index) {
2021
                        if ($this->record->getTableName().'_'.$propName.'_fk_idx' == $index) {
2022
                            $indexExists = true;
2023
                        }
2024
                    }
2025
                    if (!$indexExists) {
2026
                        $this->createForeignIndex($propName, $prop->getRelatedClass(), $prop->getRelatedClassField());
2027
                    }
2028
                }
2029
                if ($prop->getRelationType() == 'MANY-TO-MANY') {
2030
                    $lookup = $prop->getLookup();
2031
                    if ($lookup != null) {
2032
                        try {
2033
                            $lookupIndexNames = $lookup->getIndexes();
2034
                            // handle index check/creation on left side of Relation
2035
                            $indexExists = false;
2036
                            foreach ($lookupIndexNames as $index) {
2037
                                if ($lookup->getTableName().'_leftID_fk_idx' == $index) {
2038
                                    $indexExists = true;
2039
                                }
2040
                            }
2041
                            if (!$indexExists) {
2042
                                $lookup->createForeignIndex('leftID', $prop->getRelatedClass('left'), 'ID');
2043
                            }
2044
                            // handle index check/creation on right side of Relation
2045
                            $indexExists = false;
2046
                            foreach ($lookupIndexNames as $index) {
2047
                                if ($lookup->getTableName().'_rightID_fk_idx' == $index) {
2048
                                    $indexExists = true;
2049
                                }
2050
                            }
2051
                            if (!$indexExists) {
2052
                                $lookup->createForeignIndex('rightID', $prop->getRelatedClass('right'), 'ID');
2053
                            }
2054
                        } catch (AlphaException $e) {
2055
                            self::$logger->error($e->getMessage());
2056
                        }
2057
                    }
2058
                }
2059
            }
2060
        }
2061
2062
        self::$logger->debug('<<checkIndexes');
2063
    }
2064
2065
    /**
2066
     * Note that SQLite 3.6.19 is requrired for foreign key support.
2067
     *
2068
     * (non-PHPdoc)
2069
     *
2070
     * @see Alpha\Model\ActiveRecordProviderInterface::createForeignIndex()
2071
     */
2072
    public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute, $indexName = null)
2073
    {
2074
        self::$logger->info('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.'], indexName=['.$indexName.']');
2075
2076
        /*
2077
         * High-level approach
2078
         *
2079
         * 1. Rename the source table to [tablename]_temp
2080
         * 2. Create a new [tablename] table, with the new FK in place.
2081
         * 3. Copy all of the data from [tablename]_temp to [tablename].
2082
         * 4. Drop [tablename]_temp.
2083
         */
2084
        try {
2085
            // rename the table to [tablename]_temp
2086
            $query = 'ALTER TABLE '.$this->record->getTableName().' RENAME TO '.$this->record->getTableName().'_temp;';
2087
            $this->record->setLastQuery($query);
2088
            self::getConnection()->query($query);
2089
2090
            self::$logger->info('Renamed the table ['.$this->record->getTableName().'] to ['.$this->record->getTableName().'_temp]');
2091
2092
            // now create the new table with the FK in place
2093
            $record = new $relatedClass();
2094
            $tableName = $record->getTableName();
2095
            $this->foreignKeys[$attributeName] = array($tableName, $relatedClassAttribute);
2096
2097
            if (!$this->checkTableExists()) {
2098
                $this->makeTable(false);
2099
            }
2100
2101
            self::$logger->info('Made a new copy of the table ['.$this->record->getTableName().']');
2102
2103
            // copy all of the old data to the new table
2104
            $query = 'INSERT INTO '.$this->record->getTableName().' SELECT * FROM '.$this->record->getTableName().'_temp;';
2105
            $this->record->setLastQuery($query);
2106
            self::getConnection()->query($query);
2107
2108
            self::$logger->info('Copied all of the data from ['.$this->record->getTableName().'] to ['.$this->record->getTableName().'_temp]');
2109
2110
            // finally, drop the _temp table and commit the changes
2111
            $this->record->dropTable($this->record->getTableName().'_temp');
2112
2113
            self::$logger->info('Dropped the table ['.$this->record->getTableName().'_temp]');
2114
        } catch (Exception $e) {
2115
            throw new FailedIndexCreateException('Failed to create the index ['.$attributeName.'] on ['.$this->record->getTableName().'], error is ['.$e->getMessage().'], query ['.$this->record->getLastQuery().']');
2116
        }
2117
2118
        self::$logger->info('<<createForeignIndex');
2119
    }
2120
2121
    /**
2122
     * (non-PHPdoc).
2123
     *
2124
     * @see Alpha\Model\ActiveRecordProviderInterface::createUniqueIndex()
2125
     */
2126
    public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '')
2127
    {
2128
        self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
2129
2130
        $sqlQuery = '';
2131
2132
        if ($attribute2Name != '' && $attribute3Name != '') {
2133
            $sqlQuery = 'CREATE UNIQUE INDEX IF NOT EXISTS '.$attribute1Name.'_'.$attribute2Name.'_'.$attribute3Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.','.$attribute2Name.','.$attribute3Name.');';
2134
        }
2135
2136
        if ($attribute2Name != '' && $attribute3Name == '') {
2137
            $sqlQuery = 'CREATE UNIQUE INDEX IF NOT EXISTS '.$attribute1Name.'_'.$attribute2Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.','.$attribute2Name.');';
2138
        }
2139
2140
        if ($attribute2Name == '' && $attribute3Name == '') {
2141
            $sqlQuery = 'CREATE UNIQUE INDEX IF NOT EXISTS '.$attribute1Name.'_unq_idx ON '.$this->record->getTableName().' ('.$attribute1Name.');';
2142
        }
2143
2144
        $this->record->setLastQuery($sqlQuery);
2145
2146
        $result = self::getConnection()->query($sqlQuery);
2147
2148
        if ($result) {
2149
            self::$logger->debug('Successfully created the unique index on ['.$this->record->getTableName().']');
2150
        } else {
2151
            throw new FailedIndexCreateException('Failed to create the unique index on ['.$this->record->getTableName().'], error is ['.self::getConnection()->lastErrorMsg().']');
2152
        }
2153
2154
        self::$logger->debug('<<createUniqueIndex');
2155
    }
2156
2157
    /**
2158
     * (non-PHPdoc).
2159
     *
2160
     * @see Alpha\Model\ActiveRecordProviderInterface::reload()
2161
     */
2162
    public function reload()
2163
    {
2164
        self::$logger->debug('>>reload()');
2165
2166
        if (!$this->record->isTransient()) {
2167
            $this->record->load($this->record->getID());
2168
        } else {
2169
            throw new AlphaException('Cannot reload transient object from database!');
2170
        }
2171
2172
        self::$logger->debug('<<reload');
2173
    }
2174
2175
    /**
2176
     * (non-PHPdoc).
2177
     *
2178
     * @see Alpha\Model\ActiveRecordProviderInterface::checkRecordExists()
2179
     */
2180
    public function checkRecordExists($ID)
2181
    {
2182
        self::$logger->debug('>>checkRecordExists(ID=['.$ID.'])');
2183
2184
        $sqlQuery = 'SELECT ID FROM '.$this->record->getTableName().' WHERE ID = :ID;';
2185
        $this->record->setLastQuery($sqlQuery);
2186
        $stmt = self::getConnection()->prepare($sqlQuery);
2187
2188
        if ($stmt instanceof SQLite3Stmt) {
2189
            $stmt->bindValue(':ID', $ID, SQLITE3_INTEGER);
2190
2191
            $result = $stmt->execute();
2192
2193
            // there should only ever be one (or none)
2194
            $row = $result->fetchArray(SQLITE3_ASSOC);
2195
2196
            $stmt->close();
2197
        } else {
2198
            self::$logger->debug('<<checkRecordExists');
2199
            throw new AlphaException('Failed to check for the record ['.$ID.'] on the class ['.get_class($this->record).'] from the table ['.$this->record->getTableName().'], query is ['.$this->record->getLastQuery().']');
2200
        }
2201
2202
        if (!isset($row['ID'])) {
2203
            self::$logger->debug('<<checkRecordExists [false]');
2204
2205
            return false;
2206
        } else {
2207
            self::$logger->debug('<<checkRecordExists [true]');
2208
2209
            return true;
2210
        }
2211
    }
2212
2213
    /**
2214
     * (non-PHPdoc).
2215
     *
2216
     * @see Alpha\Model\ActiveRecordProviderInterface::isTableOverloaded()
2217
     */
2218
    public function isTableOverloaded()
2219
    {
2220
        self::$logger->debug('>>isTableOverloaded()');
2221
2222
        $reflection = new ReflectionClass($this->record);
2223
        $classname = $reflection->getShortName();
2224
        $tablename = ucfirst($this->record->getTableName());
2225
2226
        // use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
2227
        $implementedInterfaces = $reflection->getInterfaces();
2228
2229
        foreach ($implementedInterfaces as $interface) {
2230
            if ($interface->name == 'Alpha\Model\Type\TypeInterface') {
2231
                self::$logger->debug('<<isTableOverloaded [false]');
2232
2233
                return false;
2234
            }
2235
        }
2236
2237
        if ($classname != $tablename) {
2238
            // loop over all records to see if there is one using the same table as this record
2239
2240
            $Recordclasses = ActiveRecord::getRecordClassNames();
2241
2242
            foreach ($Recordclasses as $RecordclassName) {
2243
                $reflection = new ReflectionClass($RecordclassName);
2244
                $classname = $reflection->getShortName();
2245
                if ($tablename == $classname) {
2246
                    self::$logger->debug('<<isTableOverloaded [true]');
2247
2248
                    return true;
2249
                }
2250
            }
2251
            self::$logger->debug('<<isTableOverloaded');
2252
            throw new BadTableNameException('The table name ['.$tablename.'] for the class ['.$classname.'] is invalid as it does not match a Record definition in the system!');
2253
        } else {
2254
            // check to see if there is already a "classname" column in the database for this record
2255
            $sqlQuery = 'PRAGMA table_info('.$this->record->getTableName().')';
2256
            $result = self::getConnection()->query($sqlQuery);
2257
            $this->record->setLastQuery($sqlQuery);
2258
2259
            if (!$result) {
2260
                self::$logger->warn('Error during pragma table info lookup ['.self::getLastDatabaseError().']');
2261
            } else {
2262
                while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
2263
                    if ('classname' == $row['name']) {
2264
                        self::$logger->debug('<<isTableOverloaded [true]');
2265
2266
                        return true;
2267
                    }
2268
                }
2269
            }
2270
2271
            self::$logger->debug('<<isTableOverloaded [false]');
2272
2273
            return false;
2274
        }
2275
    }
2276
2277
    /**
2278
     * (non-PHPdoc).
2279
     *
2280
     * @see Alpha\Model\ActiveRecordProviderInterface::begin()
2281
     */
2282
    public static function begin()
2283
    {
2284
        if (self::$logger == null) {
2285
            self::$logger = new Logger('ActiveRecordProviderSQLite');
2286
        }
2287
        self::$logger->debug('>>begin()');
2288
2289
        if (!self::getConnection()->exec('BEGIN')) {
2290
            throw new AlphaException('Error beginning a new transaction, error is ['.self::getLastDatabaseError().']');
2291
        }
2292
2293
        self::$logger->debug('<<begin');
2294
    }
2295
2296
    /**
2297
     * (non-PHPdoc).
2298
     *
2299
     * @see Alpha\Model\ActiveRecordProviderInterface::commit()
2300
     */
2301
    public static function commit()
2302
    {
2303
        if (self::$logger == null) {
2304
            self::$logger = new Logger('ActiveRecordProviderSQLite');
2305
        }
2306
        self::$logger->debug('>>commit()');
2307
2308
        if (!self::getConnection()->exec('COMMIT')) {
2309
            throw new AlphaException('Error commiting a transaction, error is ['.self::getLastDatabaseError().']');
2310
        }
2311
2312
        self::$logger->debug('<<commit');
2313
    }
2314
2315
    /**
2316
     * (non-PHPdoc).
2317
     *
2318
     * @see Alpha\Model\ActiveRecordProviderInterface::rollback()
2319
     */
2320
    public static function rollback()
2321
    {
2322
        if (self::$logger == null) {
2323
            self::$logger = new Logger('ActiveRecordProviderSQLite');
2324
        }
2325
2326
        self::$logger->debug('>>rollback()');
2327
2328
        try {
2329
            self::getConnection()->exec('ROLLBACK');
2330
            self::disconnect();
2331
        } catch (Exception $e) {
2332
            if (mb_strpos($e->getMessage(), 'cannot rollback - no transaction is active') === false) { // just filtering out errors where the rollback failed due to no current transaction
2333
                throw new AlphaException('Error rolling back a transaction, error is ['.self::getLastDatabaseError().']');
2334
            }
2335
        }
2336
2337
        self::$logger->debug('<<rollback');
2338
    }
2339
2340
    /**
2341
     * (non-PHPdoc).
2342
     *
2343
     * @see Alpha\Model\ActiveRecordProviderInterface::setRecord()
2344
     */
2345
    public function setRecord($Record)
2346
    {
2347
        $this->record = $Record;
2348
    }
2349
2350
    /**
2351
     * (non-PHPdoc).
2352
     *
2353
     * @see Alpha\Model\ActiveRecordProviderInterface::checkDatabaseExists()
2354
     */
2355
    public static function checkDatabaseExists()
2356
    {
2357
        $config = ConfigProvider::getInstance();
2358
2359
        return file_exists($config->get('db.file.path'));
2360
    }
2361
2362
    /**
2363
     * (non-PHPdoc).
2364
     *
2365
     * @see Alpha\Model\ActiveRecordProviderInterface::createDatabase()
2366
     */
2367
    public static function createDatabase()
2368
    {
2369
        $config = ConfigProvider::getInstance();
2370
2371
        if (!self::checkDatabaseExists()) {
2372
            fopen($config->get('db.file.path'), 'x+');
2373
            chmod($config->get('db.file.path'), 0755);
2374
        }
2375
    }
2376
2377
    /**
2378
     * (non-PHPdoc).
2379
     *
2380
     * @see Alpha\Model\ActiveRecordProviderInterface::dropDatabase()
2381
     */
2382
    public static function dropDatabase()
2383
    {
2384
        $config = ConfigProvider::getInstance();
2385
2386
        if (self::checkDatabaseExists()) {
2387
            unlink($config->get('db.file.path'));
2388
        }
2389
    }
2390
2391
    /**
2392
     * (non-PHPdoc).
2393
     *
2394
     * @see Alpha\Model\ActiveRecordProviderInterface::backupDatabase()
2395
     */
2396
    public static function backupDatabase($targetFile)
2397
    {
2398
        $config = ConfigProvider::getInstance();
2399
2400
        exec('sqlite3 '.$config->get('db.file.path').' ".backup '.$targetFile.'"');
2401
    }
2402
}
2403