Completed
Push — master ( 7c9586...c8ce39 )
by Oscar
01:44
created

Database::__get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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