5
class Connection implements \Serializable
8
protected ?string $username;
11
protected ?string $password;
13
/** Log queries flag */
14
protected bool $log_queries = false;
17
protected array $log = [];
20
protected array $commands = [];
22
/** PDO connection options */
23
protected array $options = [
24
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
25
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ,
26
\PDO::ATTR_STRINGIFY_FETCHES => false,
27
\PDO::ATTR_EMULATE_PREPARES => false,
30
/** The PDO object associated with this connection */
33
/** The compiler associated with this connection */
34
protected ?SQL\Compiler $compiler = null;
36
/** The schema compiler associated with this connection */
37
protected ?Schema\Compiler $schema_compiler = null;
39
/** The DSN for this connection */
40
protected ?string $dsn;
43
protected ?string $driver;
45
/** Schema instance */
46
protected ?Schema $schema = null;
48
/** Compiler options */
49
protected array $compiler_options = [];
51
/** Schema compiler options */
52
protected array $schema_compiler_options = [];
54
protected bool $throw_transaction_exceptions = false;
56
public function __construct(
58
string $username = null,
59
string $password = null,
60
string $driver = null,
65
$this->username = $username;
66
$this->password = $password;
67
$this->driver = $driver;
71
public function __serialize(): array
74
'username' => $this->username,
75
'password' => $this->password,
76
'logQueries' => $this->log_queries,
77
'options' => $this->options,
78
'commands' => $this->commands,
83
public function __unserialize(array $data): void
85
foreach ($data as $key => $value) {
86
$this->{$key} = $value;
90
public static function from_pdo(\PDO $pdo): static
92
return new static(null, null, null, null, $pdo);
96
* Enable or disable query logging
98
* @param bool $value (optional) Value
100
public function log_queries(bool $value = true): static
102
$this->log_queries = $value;
107
public function throw_transaction_exceptions(bool $value = true): static
109
$this->throw_transaction_exceptions = $value;
115
* Add an init command
117
* @param string $query SQL command
118
* @param array $params (optional) Params
120
public function init_command(string $query, array $params = []): static
122
$this->commands[] = [
133
public function username(string $username): static
135
$this->username = $username;
143
public function password(string $password): static
145
$this->password = $password;
151
* Set PDO connection options
153
* @param array $options PDO options
155
public function options(array $options): static
157
foreach ($options as $name => $value) {
158
$this->option($name, $value);
165
* Set a PDO connection option
167
public function option(mixed $name, mixed $value): static
169
$this->options[$name] = $value;
175
* Use persistent connections
177
* @param bool $value (optional) Value
179
public function persistent(bool $value = true): static
181
return $this->option(\PDO::ATTR_PERSISTENT, $value);
187
* @param string $format Date format
189
public function set_date_format(string $format): static
191
$this->compiler_options['dateFormat'] = $format;
197
* Set identifier wrapper
199
* @param string $wrapper Identifier wrapper
201
public function set_wrapper_format(string $wrapper): static
203
$this->compiler_options['wrapper'] = $wrapper;
204
$this->schema_compiler_options['wrapper'] = $wrapper;
210
* Returns the DSN associated with this connection
212
public function get_dsn(): ?string
218
* Returns the driver's name
220
public function get_driver(): string
222
if ($this->driver === null) {
223
$this->driver = $this->get_pdo()->getAttribute(\PDO::ATTR_DRIVER_NAME);
226
return $this->driver;
230
* Returns the schema associated with this connection
232
public function get_schema(): Schema
234
if ($this->schema === null) {
235
$this->schema = new Schema($this);
238
return $this->schema;
242
* Returns the PDO object associated with this connection
244
public function get_pdo(): \PDO
246
if ($this->pdo == null) {
247
$this->pdo = new \PDO($this->get_dsn(), $this->username, $this->password, $this->options);
249
foreach ($this->commands as $command) {
250
$this->command($command['sql'], $command['params']);
258
* Returns an instance of the compiler associated with this connection
260
public function get_compiler(): SQL\Compiler
262
if ($this->compiler === null) {
263
$this->compiler = match ($this->get_driver()) {
264
'mysql' => new SQL\Compiler\MySQL(),
265
'dblib', 'mssql', 'sqlsrv', 'sybase' => new SQL\Compiler\SQLServer(),
266
'oci', 'oracle' => new SQL\Compiler\Oracle(),
267
'firebird' => new SQL\Compiler\Firebird(),
268
'db2', 'ibm', 'odbc' => new SQL\Compiler\DB2(),
269
'nuodb' => new SQL\Compiler\NuoDB(),
270
default => new SQL\Compiler(),
273
$this->compiler->set_options($this->compiler_options);
276
return $this->compiler;
280
* Returns an instance of the schema compiler associated with this connection
282
public function schema_compiler(): Schema\Compiler
284
if ($this->schema_compiler === null) {
285
$this->schema_compiler = match ($this->get_driver()) {
286
'mysql' => new Schema\Compiler\MySQL($this),
287
'pgsql' => new Schema\Compiler\PostgreSQL($this),
288
'dblib', 'mssql', 'sqlsrv', 'sybase' => new Schema\Compiler\SQLServer($this),
289
'sqlite' => new Schema\Compiler\SQLite($this),
290
'oci', 'oracle' => new Schema\Compiler\Oracle($this),
291
default => throw new \Exception('Schema not supported yet'),
294
$this->schema_compiler->set_options($this->schema_compiler_options);
297
return $this->schema_compiler;
301
* Close the current connection by destroying the associated PDO object
303
public function disconnect(): void
309
* Returns the query log for this database.
311
public function get_log(): array
319
* @param string $sql SQL Query
320
* @param array $params (optional) Query params
322
public function query(string $sql, array $params = [])
324
$prepared = $this->prepare($sql, $params);
325
$this->execute($prepared);
327
return new ResultSet($prepared['statement']);
331
* Execute a non-query SQL command
333
* @param string $sql SQL Command
334
* @param array $params (optional) Command params
336
public function command(string $sql, array $params = [])
338
return $this->execute($this->prepare($sql, $params));
342
* Execute a query and return the number of affected rows
344
* @param string $sql SQL Query
345
* @param array $params (optional) Query params
347
public function count(string $sql, array $params = [])
349
$prepared = $this->prepare($sql, $params);
350
$this->execute($prepared);
351
$result = $prepared['statement']->rowCount();
352
$prepared['statement']->closeCursor();
358
* Execute a query and fetch the first column
360
* @param string $sql SQL Query
361
* @param array $params (optional) Query params
365
public function column(string $sql, array $params = [])
367
$prepared = $this->prepare($sql, $params);
368
$this->execute($prepared);
369
$result = $prepared['statement']->fetchColumn();
370
$prepared['statement']->closeCursor();
378
* @throws \PDOException
380
public function transaction(callable $callback, mixed $that = null, mixed $default = null): mixed
382
if ($that === null) {
386
$pdo = $this->get_pdo();
388
if ($pdo->inTransaction()) {
389
return $callback($that);
395
$pdo->beginTransaction();
396
$result = $callback($that);
398
} catch (\PDOException $exception) {
400
if ($this->throw_transaction_exceptions) {
409
* Replace placeholders with parameters.
411
* @param string $query SQL query
412
* @param array $params Query parameters
414
protected function replace_params(string $query, array $params): string
416
$compiler = $this->get_compiler();
418
return \preg_replace_callback('/\?/', static function () use (&$params, $compiler) {
419
$param = \array_shift($params);
420
$param = \is_object($param) ? \get_class($param) : $param;
422
if (\is_int($param) || \is_float($param)) {
426
if ($param === null) {
430
if (\is_bool($param)) {
431
return $param ? 'TRUE' : 'FALSE';
434
return $compiler->quote($param);
441
* @param string $query SQL query
442
* @param array $params Query parameters
444
protected function prepare(string $query, array $params): array
447
$statement = $this->get_pdo()->prepare($query);
448
} catch (\PDOException $e) {
449
throw new \PDOException(
450
$e->getMessage() . ' [ ' . $this->replace_params($query, $params) . ' ] ',
456
return ['query' => $query, 'params' => $params, 'statement' => $statement];
460
* Executes a prepared query and returns TRUE on success or FALSE on failure.
462
* @param array $prepared Prepared query
466
protected function execute(array $prepared)
468
if ($this->log_queries) {
469
$start = \microtime(true);
471
'query' => $this->replace_params($prepared['query'], $prepared['params']),
473
$this->log[] = &$log;
477
if ($prepared['params']) {
478
$this->bind_values($prepared['statement'], $prepared['params']);
480
$result = $prepared['statement']->execute();
481
} catch (\PDOException $e) {
482
throw new \PDOException(
483
$e->getMessage() . ' [ ' . $this->replace_params($prepared['query'], $prepared['params']) . ' ] ',
489
if ($this->log_queries) {
490
$log['time'] = \microtime(true) - $start;
496
protected function bind_values(\PDOStatement $statement, array $values): void
498
foreach ($values as $key => $value) {
499
$param = \PDO::PARAM_STR;
501
if (\is_null($value)) {
502
$param = \PDO::PARAM_NULL;
503
} elseif (\is_int($value)) {
504
$param = \PDO::PARAM_INT;
505
} elseif (\is_bool($value)) {
506
$param = \PDO::PARAM_BOOL;
509
$statement->bindValue($key + 1, $value, $param);
514
* Implementation of Serializable::serialize
516
public function serialize(): string
519
'username' => $this->username,
520
'password' => $this->password,
521
'logQueries' => $this->log_queries,
522
'options' => $this->options,
523
'commands' => $this->commands,
529
* Implementation of Serializable::unserialize
531
public function unserialize(string $data): void
533
$object = \unserialize($data);
535
foreach ($object as $key => $value) {
536
$this->{$key} = $value;