Completed
Push — master ( 4fb1d6...a24b8d )
by Oscar
01:43
created

Database::createDefaultFieldFactories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 1
nc 1
nop 0
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
49
        if ($scheme) {
50
            return;
51
        }
52
53
        $engine = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
54
55
        switch ($engine) {
56
            case 'mysql':
57
                $this->scheme = new Mysql($pdo);
58
                break;
59
            case 'sqlite':
60
                $this->scheme = new Sqlite($pdo);
61
                break;
62
            default:
63
                throw new RuntimeException(sprintf('Invalid engine type: %s', $engine));
64
        }
65
66
        $this->fieldFactories = $fieldFactories ?: self::createDefaultFieldFactories();
67
    }
68
69
    /**
70
     * Configure custom classes for some tables
71
     * [table => classname]
72
     */
73
    public function setTablesClasses(array $classes): self
74
    {
75
        $this->tablesClasses = $classes;
76
77
        return $this;
78
    }
79
80
    /**
81
     * Returns a config value
82
     */
83
    public function getConfig(string $name)
84
    {
85
        return $this->config[$name] ?? null;
86
    }
87
88
    /**
89
     * Set a config value
90
     * @param mixed $value
91
     */
92
    public function setConfig(string $name, $value): self
93
    {
94
        $this->config[$name] = $value;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Return the scheme class
101
     */
102
    public function getScheme(): SchemeInterface
103
    {
104
        return $this->scheme;
105
    }
106
107
    /**
108
     * Returns the connection instance.
109
     */
110
    public function getConnection(): Connection
111
    {
112
        return $this->connection;
113
    }
114
115
    public function select(): Select
116
    {
117
        return new Select($this->connection, new Bind());
118
    }
119
120
    public function update(): Update
121
    {
122
        return new Update($this->connection, new Bind());
123
    }
124
125
    public function delete(): Delete
126
    {
127
        return new Delete($this->connection, new Bind());
128
    }
129
130
    public function insert(): Insert
131
    {
132
        return new Insert($this->connection, new Bind());
133
    }
134
135
    public function setFieldFactory(FieldFactory $fieldFactory): self
136
    {
137
        $this->fieldFactories[$fieldFactory->getClassName()] = $fieldFactory;
138
139
        return $this;
140
    }
141
142
    public function getFieldFactory(string $className): FieldFactory
143
    {
144
        return $this->fieldFactories[$className];
145
    }
146
147
    public function getFieldFactories(): array
148
    {
149
        return $this->fieldFactories;
150
    }
151
152
    /**
153
     * Magic method to initialize the tables in lazy mode.
154
     *
155
     * @throws SimpleCrudException If the table cannot be instantiated
156
     */
157
    public function __get(string $name): Table
158
    {
159
        if (isset($this->tables[$name])) {
160
            return $this->tables[$name];
161
        }
162
163
        if (!$this->__isset($name)) {
164
            throw new InvalidArgumentException(
165
                sprintf('The table "%s" does not exist', $name)
166
            );
167
        }
168
169
        $class = $this->tablesClasses[$name] ?? Table::class;
170
171
        return $this->tables[$name] = new $class($this, $name);
172
    }
173
174
    /**
175
     * Magic method to check if a table exists or not.
176
     */
177
    public function __isset(string $name): bool
178
    {
179
        return in_array($name, $this->getScheme()->getTables());
180
    }
181
182
    /**
183
     * Execute a query and returns the statement object with the result.
184
     *
185
     * @throws Exception
186
     */
187
    public function execute(string $query, array $marks = null): PDOStatement
188
    {
189
        $statement = $this->connection->prepare($query);
190
        $statement->execute($marks);
191
192
        return $statement;
193
    }
194
195
    /**
196
     * Execute a callable inside a transaction.
197
     *
198
     * @return mixed The callable returned value
199
     */
200
    public function executeTransaction(callable $callable)
201
    {
202
        try {
203
            $transaction = $this->beginTransaction();
204
205
            $return = $callable($this);
206
207
            if ($transaction) {
208
                $this->commit();
209
            }
210
        } catch (Exception $exception) {
211
            if ($transaction) {
212
                $this->rollBack();
213
            }
214
215
            throw $exception;
216
        }
217
218
        return $return;
219
    }
220
221
    /**
222
     * Returns the last insert id.
223
     */
224
    public function lastInsertId(): string
225
    {
226
        return $this->connection->lastInsertId();
227
    }
228
229
    /**
230
     * Starts a transaction if it's not started yet.
231
     */
232
    public function beginTransaction(): bool
233
    {
234
        if (!$this->inTransaction()) {
235
            $this->connection->beginTransaction();
236
237
            return $this->inTransaction = true;
238
        }
239
240
        return false;
241
    }
242
243
    /**
244
     * Commits the changes of the transaction to the database.
245
     */
246
    public function commit()
247
    {
248
        if ($this->inTransaction()) {
249
            $this->connection->commit();
250
            $this->inTransaction = false;
251
        }
252
    }
253
254
    /**
255
     * RollBack a transaction.
256
     */
257
    public function rollBack()
258
    {
259
        if ($this->inTransaction()) {
260
            $this->connection->rollBack();
261
            $this->inTransaction = false;
262
        }
263
    }
264
265
    /**
266
     * Check if there is a transaction opened currently in this adapter.
267
     */
268
    public function inTransaction()
269
    {
270
        return ($this->inTransaction === true) && ($this->connection->inTransaction() === true);
271
    }
272
273
    private static function createDefaultFieldFactories(): array
274
    {
275
        return [
276
            Boolean::class => Boolean::getFactory(),
277
            Date::class => Date::getFactory(),
278
            Datetime::class => Datetime::getFactory(),
279
            Decimal::class => Decimal::getFactory(),
280
            Field::class => Field::getFactory(),
281
            Integer::class => Integer::getFactory(),
282
            Json::class => Json::getFactory(),
283
            Point::class => Point::getFactory(),
284
            Set::class => Set::getFactory(),
285
            Serializable::class => Serializable::getFactory(),
286
        ];
287
    }
288
}
289