Completed
Push — master ( 0ad69c...4e4c33 )
by Oscar
01:28
created

Database   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 20

Importance

Changes 0
Metric Value
wmc 38
lcom 4
cbo 20
dl 0
loc 270
rs 9.36
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 5
A setTablesClasses() 0 6 1
A getConfig() 0 4 1
A setConfig() 0 6 1
A getScheme() 0 4 1
A getConnection() 0 4 1
A select() 0 4 1
A update() 0 4 1
A delete() 0 4 1
A insert() 0 4 1
A setFieldFactory() 0 6 1
A getFieldFactory() 0 4 1
A getFieldFactories() 0 4 1
A clearCache() 0 8 2
A __get() 0 16 3
A __isset() 0 4 1
A execute() 0 7 1
A executeTransaction() 0 20 4
A lastInsertId() 0 4 1
A beginTransaction() 0 10 2
A commit() 0 7 2
A rollBack() 0 7 2
A inTransaction() 0 4 2
A createDefaultFieldFactories() 0 16 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace SimpleCrud;
5
6
use Atlas\Pdo\Connection;
7
use Atlas\Query\Bind;
8
use Atlas\Query\Delete;
9
use Atlas\Query\Insert;
10
use Atlas\Query\Select;
11
use Atlas\Query\Update;
12
use Exception;
13
use InvalidArgumentException;
14
use PDO;
15
use PDOStatement;
16
use SimpleCrud\Fields\Boolean;
17
use SimpleCrud\Fields\Date;
18
use SimpleCrud\Fields\Datetime;
19
use SimpleCrud\Fields\Decimal;
20
use SimpleCrud\Fields\Field;
21
use SimpleCrud\Fields\FieldFactory;
22
use SimpleCrud\Fields\Integer;
23
use SimpleCrud\Fields\Json;
24
use SimpleCrud\Fields\Point;
25
use SimpleCrud\Fields\Serializable;
26
use SimpleCrud\Fields\Set;
27
use SimpleCrud\Scheme\Mysql;
28
use SimpleCrud\Scheme\SchemeInterface;
29
use SimpleCrud\Scheme\Sqlite;
30
31
final class Database
32
{
33
    const CONFIG_LOCALE = 'locale';
34
35
    private $connection;
36
    private $scheme;
37
    private $tables = [];
38
    private $tablesClasses = [];
39
    private $inTransaction = false;
40
    private $onExecute;
41
    private $config = [];
42
    private $fieldFactories = [];
43
44
    public function __construct(PDO $pdo, SchemeInterface $scheme = null, array $fieldFactories = null)
45
    {
46
        $this->connection = new Connection($pdo);
47
        $this->scheme = $scheme;
48
        $this->fieldFactories = $fieldFactories ?: self::createDefaultFieldFactories();
49
50
        if ($scheme) {
51
            return;
52
        }
53
54
        $engine = $this->connection->getDriverName();
55
56
        switch ($engine) {
57
            case 'mysql':
58
                $this->scheme = new Mysql($pdo);
59
                break;
60
            case 'sqlite':
61
                $this->scheme = new Sqlite($pdo);
62
                break;
63
            default:
64
                throw new RuntimeException(sprintf('Invalid engine type: %s', $engine));
65
        }
66
    }
67
68
    /**
69
     * Configure custom classes for some tables
70
     * [table => classname]
71
     */
72
    public function setTablesClasses(array $classes): self
73
    {
74
        $this->tablesClasses = $classes;
75
76
        return $this;
77
    }
78
79
    /**
80
     * Returns a config value
81
     */
82
    public function getConfig(string $name)
83
    {
84
        return $this->config[$name] ?? null;
85
    }
86
87
    /**
88
     * Set a config value
89
     * @param mixed $value
90
     */
91
    public function setConfig(string $name, $value): self
92
    {
93
        $this->config[$name] = $value;
94
95
        return $this;
96
    }
97
98
    /**
99
     * Return the scheme class
100
     */
101
    public function getScheme(): SchemeInterface
102
    {
103
        return $this->scheme;
104
    }
105
106
    /**
107
     * Returns the connection instance.
108
     */
109
    public function getConnection(): Connection
110
    {
111
        return $this->connection;
112
    }
113
114
    public function select(): Select
115
    {
116
        return new Select($this->connection, new Bind());
117
    }
118
119
    public function update(): Update
120
    {
121
        return new Update($this->connection, new Bind());
122
    }
123
124
    public function delete(): Delete
125
    {
126
        return new Delete($this->connection, new Bind());
127
    }
128
129
    public function insert(): Insert
130
    {
131
        return new Insert($this->connection, new Bind());
132
    }
133
134
    public function setFieldFactory(FieldFactory $fieldFactory): self
135
    {
136
        $this->fieldFactories[$fieldFactory->getClassName()] = $fieldFactory;
137
138
        return $this;
139
    }
140
141
    public function getFieldFactory(string $className): FieldFactory
142
    {
143
        return $this->fieldFactories[$className];
144
    }
145
146
    public function getFieldFactories(): array
147
    {
148
        return $this->fieldFactories;
149
    }
150
151
    /**
152
     * Clear the cache of all tables
153
     */
154
    public function clearCache(): self
155
    {
156
        foreach ($this->tables as $table) {
157
            $table->clearCache();
158
        }
159
160
        return $this;
161
    }
162
163
    /**
164
     * Magic method to initialize the tables in lazy mode.
165
     *
166
     * @throws SimpleCrudException If the table cannot be instantiated
167
     */
168
    public function __get(string $name): Table
169
    {
170
        if (isset($this->tables[$name])) {
171
            return $this->tables[$name];
172
        }
173
174
        if (!$this->__isset($name)) {
175
            throw new InvalidArgumentException(
176
                sprintf('The table "%s" does not exist', $name)
177
            );
178
        }
179
180
        $class = $this->tablesClasses[$name] ?? Table::class;
181
182
        return $this->tables[$name] = new $class($this, $name);
183
    }
184
185
    /**
186
     * Magic method to check if a table exists or not.
187
     */
188
    public function __isset(string $name): bool
189
    {
190
        return in_array($name, $this->getScheme()->getTables());
191
    }
192
193
    /**
194
     * Execute a query and returns the statement object with the result.
195
     *
196
     * @throws Exception
197
     */
198
    public function execute(string $query, array $marks = null): PDOStatement
199
    {
200
        $statement = $this->connection->prepare($query);
201
        $statement->execute($marks);
202
203
        return $statement;
204
    }
205
206
    /**
207
     * Execute a callable inside a transaction.
208
     *
209
     * @return mixed The callable returned value
210
     */
211
    public function executeTransaction(callable $callable)
212
    {
213
        try {
214
            $transaction = $this->beginTransaction();
215
216
            $return = $callable($this);
217
218
            if ($transaction) {
219
                $this->commit();
220
            }
221
        } catch (Exception $exception) {
222
            if ($transaction) {
223
                $this->rollBack();
224
            }
225
226
            throw $exception;
227
        }
228
229
        return $return;
230
    }
231
232
    /**
233
     * Returns the last insert id.
234
     */
235
    public function lastInsertId(): string
236
    {
237
        return $this->connection->lastInsertId();
238
    }
239
240
    /**
241
     * Starts a transaction if it's not started yet.
242
     */
243
    public function beginTransaction(): bool
244
    {
245
        if (!$this->inTransaction()) {
246
            $this->connection->beginTransaction();
247
248
            return $this->inTransaction = true;
249
        }
250
251
        return false;
252
    }
253
254
    /**
255
     * Commits the changes of the transaction to the database.
256
     */
257
    public function commit()
258
    {
259
        if ($this->inTransaction()) {
260
            $this->connection->commit();
261
            $this->inTransaction = false;
262
        }
263
    }
264
265
    /**
266
     * RollBack a transaction.
267
     */
268
    public function rollBack()
269
    {
270
        if ($this->inTransaction()) {
271
            $this->connection->rollBack();
272
            $this->inTransaction = false;
273
        }
274
    }
275
276
    /**
277
     * Check if there is a transaction opened currently in this adapter.
278
     */
279
    public function inTransaction()
280
    {
281
        return ($this->inTransaction === true) && ($this->connection->inTransaction() === true);
282
    }
283
284
    private static function createDefaultFieldFactories(): array
285
    {
286
        return [
287
            Field::class => Field::getFactory(),
288
289
            Boolean::class => Boolean::getFactory(),
290
            Date::class => Date::getFactory(),
291
            Datetime::class => Datetime::getFactory(),
292
            Decimal::class => Decimal::getFactory(),
293
            Integer::class => Integer::getFactory(),
294
            Json::class => Json::getFactory(),
295
            Point::class => Point::getFactory(),
296
            Set::class => Set::getFactory(),
297
            Serializable::class => Serializable::getFactory(),
298
        ];
299
    }
300
}
301