|
|
@@ -5,6 +5,8 @@
|
|
|
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
|
|
|
*/
|
|
|
|
|
|
+declare(strict_types=1);
|
|
|
+
|
|
|
namespace Dibi;
|
|
|
|
|
|
use Traversable;
|
|
|
@@ -16,109 +18,92 @@ use Traversable;
|
|
|
* @property-read int $affectedRows
|
|
|
* @property-read int $insertId
|
|
|
*/
|
|
|
-class Connection
|
|
|
+class Connection implements IConnection
|
|
|
{
|
|
|
use Strict;
|
|
|
|
|
|
/** @var array of function (Event $event); Occurs after query is executed */
|
|
|
- public $onEvent;
|
|
|
+ public $onEvent = [];
|
|
|
|
|
|
/** @var array Current connection configuration */
|
|
|
private $config;
|
|
|
|
|
|
- /** @var Driver */
|
|
|
+ /** @var string[] resultset formats */
|
|
|
+ private $formats;
|
|
|
+
|
|
|
+ /** @var Driver|null */
|
|
|
private $driver;
|
|
|
|
|
|
/** @var Translator|null */
|
|
|
private $translator;
|
|
|
|
|
|
- /** @var bool Is connected? */
|
|
|
- private $connected = false;
|
|
|
-
|
|
|
/** @var HashMap Substitutes for identifiers */
|
|
|
private $substitutes;
|
|
|
|
|
|
+ private $transactionDepth = 0;
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* Connection options: (see driver-specific options too)
|
|
|
* - lazy (bool) => if true, connection will be established only when required
|
|
|
* - result (array) => result set options
|
|
|
- * - formatDateTime => date-time format (if empty, DateTime objects will be returned)
|
|
|
- * - profiler (array or bool)
|
|
|
+ * - normalize => normalizes result fields (default: true)
|
|
|
+ * - formatDateTime => date-time format
|
|
|
+ * empty for decoding as Dibi\DateTime (default)
|
|
|
+ * "..." formatted according to given format, see https://www.php.net/manual/en/datetime.format.php
|
|
|
+ * "native" for leaving value as is
|
|
|
+ * - formatTimeInterval => time-interval format
|
|
|
+ * empty for decoding as DateInterval (default)
|
|
|
+ * "..." formatted according to given format, see https://www.php.net/manual/en/dateinterval.format.php
|
|
|
+ * "native" for leaving value as is
|
|
|
+ * - formatJson => json format
|
|
|
+ * "array" for decoding json as an array (default)
|
|
|
+ * "object" for decoding json as \stdClass
|
|
|
+ * "native" for leaving value as is
|
|
|
+ * - profiler (array)
|
|
|
* - run (bool) => enable profiler?
|
|
|
* - file => file to log
|
|
|
+ * - errorsOnly (bool) => log only errors
|
|
|
* - substitutes (array) => map of driver specific substitutes (under development)
|
|
|
- * @param mixed connection parameters
|
|
|
- * @param string connection name
|
|
|
+ * - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function __construct($config, $name = null)
|
|
|
+ public function __construct(array $config, string $name = null)
|
|
|
{
|
|
|
- if (is_string($config)) {
|
|
|
- parse_str($config, $config);
|
|
|
-
|
|
|
- } elseif ($config instanceof Traversable) {
|
|
|
- $tmp = [];
|
|
|
- foreach ($config as $key => $val) {
|
|
|
- $tmp[$key] = $val instanceof Traversable ? iterator_to_array($val) : $val;
|
|
|
- }
|
|
|
- $config = $tmp;
|
|
|
-
|
|
|
- } elseif (!is_array($config)) {
|
|
|
- throw new \InvalidArgumentException('Configuration must be array, string or object.');
|
|
|
- }
|
|
|
-
|
|
|
Helpers::alias($config, 'username', 'user');
|
|
|
Helpers::alias($config, 'password', 'pass');
|
|
|
Helpers::alias($config, 'host', 'hostname');
|
|
|
Helpers::alias($config, 'result|formatDate', 'resultDate');
|
|
|
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
|
|
|
-
|
|
|
- if (!isset($config['driver'])) {
|
|
|
- $config['driver'] = \dibi::$defaultDriver;
|
|
|
- }
|
|
|
-
|
|
|
- if ($config['driver'] instanceof Driver) {
|
|
|
- $this->driver = $config['driver'];
|
|
|
- $config['driver'] = get_class($this->driver);
|
|
|
- } elseif (is_subclass_of($config['driver'], 'Dibi\Driver')) {
|
|
|
- $this->driver = new $config['driver'];
|
|
|
- } else {
|
|
|
- $class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($config['driver'])));
|
|
|
- $class = "Dibi\\Drivers\\{$class}Driver";
|
|
|
- if (!class_exists($class)) {
|
|
|
- throw new Exception("Unable to create instance of dibi driver '$class'.");
|
|
|
- }
|
|
|
- $this->driver = new $class;
|
|
|
- }
|
|
|
-
|
|
|
+ $config['driver'] = $config['driver'] ?? 'mysqli';
|
|
|
$config['name'] = $name;
|
|
|
$this->config = $config;
|
|
|
|
|
|
- // profiler
|
|
|
- $profilerCfg = &$config['profiler'];
|
|
|
- if (is_scalar($profilerCfg)) {
|
|
|
- $profilerCfg = ['run' => (bool) $profilerCfg];
|
|
|
- }
|
|
|
- if (!empty($profilerCfg['run'])) {
|
|
|
- $filter = isset($profilerCfg['filter']) ? $profilerCfg['filter'] : Event::QUERY;
|
|
|
-
|
|
|
- if (isset($profilerCfg['file'])) {
|
|
|
- $this->onEvent[] = [new Loggers\FileLogger($profilerCfg['file'], $filter), 'logEvent'];
|
|
|
- }
|
|
|
+ $this->formats = [
|
|
|
+ Type::DATE => $this->config['result']['formatDate'],
|
|
|
+ Type::DATETIME => $this->config['result']['formatDateTime'],
|
|
|
+ Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
|
|
+ Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
|
|
|
+ ];
|
|
|
|
|
|
- if (Loggers\FirePhpLogger::isAvailable()) {
|
|
|
- $this->onEvent[] = [new Loggers\FirePhpLogger($filter), 'logEvent'];
|
|
|
- }
|
|
|
+ // profiler
|
|
|
+ if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
|
|
|
+ $filter = $config['profiler']['filter'] ?? Event::QUERY;
|
|
|
+ $errorsOnly = $config['profiler']['errorsOnly'] ?? false;
|
|
|
+ $this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
|
|
|
}
|
|
|
|
|
|
- $this->substitutes = new HashMap(function ($expr) { return ":$expr:"; });
|
|
|
+ $this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; });
|
|
|
if (!empty($config['substitutes'])) {
|
|
|
foreach ($config['substitutes'] as $key => $value) {
|
|
|
$this->substitutes->$key = $value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (isset($config['onConnect']) && !is_array($config['onConnect'])) {
|
|
|
+ throw new \InvalidArgumentException("Configuration option 'onConnect' must be array.");
|
|
|
+ }
|
|
|
+
|
|
|
if (empty($config['lazy'])) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
@@ -127,11 +112,10 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Automatically frees the resources allocated for this result set.
|
|
|
- * @return void
|
|
|
*/
|
|
|
public function __destruct()
|
|
|
{
|
|
|
- if ($this->connected && $this->driver->getResource()) {
|
|
|
+ if ($this->driver && $this->driver->getResource()) {
|
|
|
$this->disconnect();
|
|
|
}
|
|
|
}
|
|
|
@@ -139,19 +123,40 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Connects to a database.
|
|
|
- * @return void
|
|
|
*/
|
|
|
- final public function connect()
|
|
|
+ final public function connect(): void
|
|
|
{
|
|
|
+ if ($this->config['driver'] instanceof Driver) {
|
|
|
+ $this->driver = $this->config['driver'];
|
|
|
+ $this->translator = new Translator($this);
|
|
|
+ return;
|
|
|
+
|
|
|
+ } elseif (is_subclass_of($this->config['driver'], Driver::class)) {
|
|
|
+ $class = $this->config['driver'];
|
|
|
+
|
|
|
+ } else {
|
|
|
+ $class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($this->config['driver'])));
|
|
|
+ $class = "Dibi\\Drivers\\{$class}Driver";
|
|
|
+ if (!class_exists($class)) {
|
|
|
+ throw new Exception("Unable to create instance of Dibi driver '$class'.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
|
|
|
try {
|
|
|
- $this->driver->connect($this->config);
|
|
|
- $this->connected = true;
|
|
|
+ $this->driver = new $class($this->config);
|
|
|
+ $this->translator = new Translator($this);
|
|
|
+
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done());
|
|
|
}
|
|
|
+ if (isset($this->config['onConnect'])) {
|
|
|
+ foreach ($this->config['onConnect'] as $sql) {
|
|
|
+ $this->query($sql);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
+ } catch (DriverException $e) {
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($e));
|
|
|
}
|
|
|
@@ -162,61 +167,44 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Disconnects from a database.
|
|
|
- * @return void
|
|
|
*/
|
|
|
- final public function disconnect()
|
|
|
+ final public function disconnect(): void
|
|
|
{
|
|
|
- $this->driver->disconnect();
|
|
|
- $this->connected = false;
|
|
|
+ if ($this->driver) {
|
|
|
+ $this->driver->disconnect();
|
|
|
+ $this->driver = $this->translator = null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Returns true when connection was established.
|
|
|
- * @return bool
|
|
|
*/
|
|
|
- final public function isConnected()
|
|
|
+ final public function isConnected(): bool
|
|
|
{
|
|
|
- return $this->connected;
|
|
|
+ return (bool) $this->driver;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Returns configuration variable. If no $key is passed, returns the entire array.
|
|
|
* @see self::__construct
|
|
|
- * @param string
|
|
|
- * @param mixed default value to use if key not found
|
|
|
* @return mixed
|
|
|
*/
|
|
|
- final public function getConfig($key = null, $default = null)
|
|
|
+ final public function getConfig(string $key = null, $default = null)
|
|
|
{
|
|
|
- if ($key === null) {
|
|
|
- return $this->config;
|
|
|
-
|
|
|
- } elseif (isset($this->config[$key])) {
|
|
|
- return $this->config[$key];
|
|
|
-
|
|
|
- } else {
|
|
|
- return $default;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /** @deprecated */
|
|
|
- public static function alias(&$config, $key, $alias)
|
|
|
- {
|
|
|
- trigger_error(__METHOD__ . '() is deprecated, use Helpers::alias().', E_USER_DEPRECATED);
|
|
|
- Helpers::alias($config, $key, $alias);
|
|
|
+ return $key === null
|
|
|
+ ? $this->config
|
|
|
+ : ($this->config[$key] ?? $default);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Returns the driver and connects to a database in lazy mode.
|
|
|
- * @return Driver
|
|
|
*/
|
|
|
- final public function getDriver()
|
|
|
+ final public function getDriver(): Driver
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
return $this->driver;
|
|
|
@@ -225,40 +213,37 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Generates (translates) and executes SQL query.
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return Result|int result set or number of affected rows
|
|
|
+ * @param mixed ...$args
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- final public function query($args)
|
|
|
+ final public function query(...$args): Result
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
- return $this->nativeQuery($this->translateArgs($args));
|
|
|
+ return $this->nativeQuery($this->translate(...$args));
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Generates SQL query.
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return string
|
|
|
+ * @param mixed ...$args
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- final public function translate($args)
|
|
|
+ final public function translate(...$args): string
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
- return $this->translateArgs($args);
|
|
|
+ if (!$this->driver) {
|
|
|
+ $this->connect();
|
|
|
+ }
|
|
|
+ return (clone $this->translator)->translate($args);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Generates and prints SQL query.
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return bool
|
|
|
+ * @param mixed ...$args
|
|
|
*/
|
|
|
- final public function test($args)
|
|
|
+ final public function test(...$args): bool
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
try {
|
|
|
- Helpers::dump($this->translateArgs($args));
|
|
|
+ Helpers::dump($this->translate(...$args));
|
|
|
return true;
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
@@ -274,44 +259,22 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Generates (translates) and returns SQL query as DataSource.
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return DataSource
|
|
|
+ * @param mixed ...$args
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- final public function dataSource($args)
|
|
|
- {
|
|
|
- $args = func_get_args();
|
|
|
- return new DataSource($this->translateArgs($args), $this);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * Generates SQL query.
|
|
|
- * @param array
|
|
|
- * @return string
|
|
|
- */
|
|
|
- protected function translateArgs($args)
|
|
|
+ final public function dataSource(...$args): DataSource
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
- $this->connect();
|
|
|
- }
|
|
|
- if (!$this->translator) {
|
|
|
- $this->translator = new Translator($this);
|
|
|
- }
|
|
|
- $translator = clone $this->translator;
|
|
|
- return $translator->translate($args);
|
|
|
+ return new DataSource($this->translate(...$args), $this);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Executes the SQL query.
|
|
|
- * @param string SQL statement.
|
|
|
- * @return Result|int result set or number of affected rows
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- final public function nativeQuery($sql)
|
|
|
+ final public function nativeQuery(string $sql): Result
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
|
|
|
@@ -320,19 +283,14 @@ class Connection
|
|
|
try {
|
|
|
$res = $this->driver->query($sql);
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
+ } catch (DriverException $e) {
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($e));
|
|
|
}
|
|
|
throw $e;
|
|
|
}
|
|
|
|
|
|
- if ($res) {
|
|
|
- $res = $this->createResultSet($res);
|
|
|
- } else {
|
|
|
- $res = $this->driver->getAffectedRows();
|
|
|
- }
|
|
|
-
|
|
|
+ $res = $this->createResultSet($res ?: new Drivers\NoDataResult(max(0, $this->driver->getAffectedRows())));
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($res));
|
|
|
}
|
|
|
@@ -342,69 +300,48 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
|
|
|
- * @return int number of rows
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function getAffectedRows()
|
|
|
+ public function getAffectedRows(): int
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
$rows = $this->driver->getAffectedRows();
|
|
|
- if (!is_int($rows) || $rows < 0) {
|
|
|
+ if ($rows === null || $rows < 0) {
|
|
|
throw new Exception('Cannot retrieve number of affected rows.');
|
|
|
}
|
|
|
return $rows;
|
|
|
}
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @deprecated
|
|
|
- */
|
|
|
- public function affectedRows()
|
|
|
- {
|
|
|
- trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
|
|
|
- return $this->getAffectedRows();
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
/**
|
|
|
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
|
|
|
- * @param string optional sequence name
|
|
|
- * @return int
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function getInsertId($sequence = null)
|
|
|
+ public function getInsertId(string $sequence = null): int
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
$id = $this->driver->getInsertId($sequence);
|
|
|
- if ($id < 1) {
|
|
|
+ if ($id === null) {
|
|
|
throw new Exception('Cannot retrieve last generated ID.');
|
|
|
}
|
|
|
- return Helpers::intVal($id);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- /**
|
|
|
- * @deprecated
|
|
|
- */
|
|
|
- public function insertId($sequence = null)
|
|
|
- {
|
|
|
- trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
|
|
|
- return $this->getInsertId($sequence);
|
|
|
+ return $id;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Begins a transaction (if supported).
|
|
|
- * @param string optional savepoint name
|
|
|
- * @return void
|
|
|
*/
|
|
|
- public function begin($savepoint = null)
|
|
|
+ public function begin(string $savepoint = null): void
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if ($this->transactionDepth !== 0) {
|
|
|
+ throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
|
|
|
@@ -414,7 +351,7 @@ class Connection
|
|
|
$this->onEvent($event->done());
|
|
|
}
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
+ } catch (DriverException $e) {
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($e));
|
|
|
}
|
|
|
@@ -425,12 +362,14 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Commits statements in a transaction.
|
|
|
- * @param string optional savepoint name
|
|
|
- * @return void
|
|
|
*/
|
|
|
- public function commit($savepoint = null)
|
|
|
+ public function commit(string $savepoint = null): void
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if ($this->transactionDepth !== 0) {
|
|
|
+ throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
|
|
|
@@ -440,7 +379,7 @@ class Connection
|
|
|
$this->onEvent($event->done());
|
|
|
}
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
+ } catch (DriverException $e) {
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($e));
|
|
|
}
|
|
|
@@ -451,12 +390,14 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Rollback changes in a transaction.
|
|
|
- * @param string optional savepoint name
|
|
|
- * @return void
|
|
|
*/
|
|
|
- public function rollback($savepoint = null)
|
|
|
+ public function rollback(string $savepoint = null): void
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if ($this->transactionDepth !== 0) {
|
|
|
+ throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
|
|
|
@@ -466,7 +407,7 @@ class Connection
|
|
|
$this->onEvent($event->done());
|
|
|
}
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
+ } catch (DriverException $e) {
|
|
|
if ($event) {
|
|
|
$this->onEvent($event->done($e));
|
|
|
}
|
|
|
@@ -475,78 +416,80 @@ class Connection
|
|
|
}
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
+ * @return mixed
|
|
|
+ */
|
|
|
+ public function transaction(callable $callback)
|
|
|
+ {
|
|
|
+ if ($this->transactionDepth === 0) {
|
|
|
+ $this->begin();
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->transactionDepth++;
|
|
|
+ try {
|
|
|
+ $res = $callback($this);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $this->transactionDepth--;
|
|
|
+ if ($this->transactionDepth === 0) {
|
|
|
+ $this->rollback();
|
|
|
+ }
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->transactionDepth--;
|
|
|
+ if ($this->transactionDepth === 0) {
|
|
|
+ $this->commit();
|
|
|
+ }
|
|
|
+
|
|
|
+ return $res;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* Result set factory.
|
|
|
- * @param ResultDriver
|
|
|
- * @return Result
|
|
|
*/
|
|
|
- public function createResultSet(ResultDriver $resultDriver)
|
|
|
+ public function createResultSet(ResultDriver $resultDriver): Result
|
|
|
{
|
|
|
- $res = new Result($resultDriver);
|
|
|
- return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
|
|
|
- ->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
|
|
|
+ return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
|
|
|
+ ->setFormats($this->formats);
|
|
|
}
|
|
|
|
|
|
|
|
|
/********************* fluent SQL builders ****************d*g**/
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @return Fluent
|
|
|
- */
|
|
|
- public function command()
|
|
|
+ public function command(): Fluent
|
|
|
{
|
|
|
return new Fluent($this);
|
|
|
}
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @param mixed column name
|
|
|
- * @return Fluent
|
|
|
- */
|
|
|
- public function select($args)
|
|
|
+ public function select(...$args): Fluent
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
- return $this->command()->__call('select', $args);
|
|
|
+ return $this->command()->select(...$args);
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * @param string table
|
|
|
- * @param array
|
|
|
- * @return Fluent
|
|
|
+ * @param string|string[] $table
|
|
|
*/
|
|
|
- public function update($table, $args)
|
|
|
+ public function update($table, iterable $args): Fluent
|
|
|
{
|
|
|
- if (!(is_array($args) || $args instanceof Traversable)) {
|
|
|
- throw new \InvalidArgumentException('Arguments must be array or Traversable.');
|
|
|
- }
|
|
|
return $this->command()->update('%n', $table)->set($args);
|
|
|
}
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @param string table
|
|
|
- * @param array
|
|
|
- * @return Fluent
|
|
|
- */
|
|
|
- public function insert($table, $args)
|
|
|
+ public function insert(string $table, iterable $args): Fluent
|
|
|
{
|
|
|
if ($args instanceof Traversable) {
|
|
|
$args = iterator_to_array($args);
|
|
|
- } elseif (!is_array($args)) {
|
|
|
- throw new \InvalidArgumentException('Arguments must be array or Traversable.');
|
|
|
}
|
|
|
return $this->command()->insert()
|
|
|
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
|
|
|
}
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @param string table
|
|
|
- * @return Fluent
|
|
|
- */
|
|
|
- public function delete($table)
|
|
|
+ public function delete(string $table): Fluent
|
|
|
{
|
|
|
return $this->command()->delete()->from('%n', $table);
|
|
|
}
|
|
|
@@ -557,9 +500,8 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Returns substitution hashmap.
|
|
|
- * @return HashMap
|
|
|
*/
|
|
|
- public function getSubstitutes()
|
|
|
+ public function getSubstitutes(): HashMap
|
|
|
{
|
|
|
return $this->substitutes;
|
|
|
}
|
|
|
@@ -567,13 +509,12 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Provides substitution.
|
|
|
- * @return string
|
|
|
*/
|
|
|
- public function substitute($value)
|
|
|
+ public function substitute(string $value): string
|
|
|
{
|
|
|
return strpos($value, ':') === false
|
|
|
? $value
|
|
|
- : preg_replace_callback('#:([^:\s]*):#', function ($m) { return $this->substitutes->{$m[1]}; }, $value);
|
|
|
+ : preg_replace_callback('#:([^:\s]*):#', function (array $m) { return $this->substitutes->{$m[1]}; }, $value);
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -582,75 +523,71 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Executes SQL query and fetch result - shortcut for query() & fetch().
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return Row|false
|
|
|
+ * @param mixed ...$args
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function fetch($args)
|
|
|
+ public function fetch(...$args): ?Row
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
return $this->query($args)->fetch();
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Executes SQL query and fetch results - shortcut for query() & fetchAll().
|
|
|
- * @param array|mixed one or more arguments
|
|
|
+ * @param mixed ...$args
|
|
|
* @return Row[]|array[]
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function fetchAll($args)
|
|
|
+ public function fetchAll(...$args): array
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
return $this->query($args)->fetchAll();
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
|
|
|
- * @param array|mixed one or more arguments
|
|
|
+ * @param mixed ...$args
|
|
|
* @return mixed
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function fetchSingle($args)
|
|
|
+ public function fetchSingle(...$args)
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
return $this->query($args)->fetchSingle();
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
|
|
|
- * @param array|mixed one or more arguments
|
|
|
- * @return array
|
|
|
+ * @param mixed ...$args
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
- public function fetchPairs($args)
|
|
|
+ public function fetchPairs(...$args): array
|
|
|
{
|
|
|
- $args = func_get_args();
|
|
|
return $this->query($args)->fetchPairs();
|
|
|
}
|
|
|
|
|
|
|
|
|
- /**
|
|
|
- * @return Literal
|
|
|
- */
|
|
|
- public static function literal($value)
|
|
|
+ public static function literal(string $value): Literal
|
|
|
{
|
|
|
return new Literal($value);
|
|
|
}
|
|
|
|
|
|
|
|
|
+ public static function expression(...$args): Expression
|
|
|
+ {
|
|
|
+ return new Expression(...$args);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/********************* misc ****************d*g**/
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Import SQL dump from file.
|
|
|
- * @param string filename
|
|
|
- * @param callable function (int $count, ?float $percent): void
|
|
|
+ * @param callable $onProgress function (int $count, ?float $percent): void
|
|
|
* @return int count of sql commands
|
|
|
*/
|
|
|
- public function loadFile($file, callable $onProgress = null)
|
|
|
+ public function loadFile(string $file, callable $onProgress = null): int
|
|
|
{
|
|
|
return Helpers::loadFromFile($this, $file, $onProgress);
|
|
|
}
|
|
|
@@ -658,14 +595,13 @@ class Connection
|
|
|
|
|
|
/**
|
|
|
* Gets a information about the current database.
|
|
|
- * @return Reflection\Database
|
|
|
*/
|
|
|
- public function getDatabaseInfo()
|
|
|
+ public function getDatabaseInfo(): Reflection\Database
|
|
|
{
|
|
|
- if (!$this->connected) {
|
|
|
+ if (!$this->driver) {
|
|
|
$this->connect();
|
|
|
}
|
|
|
- return new Reflection\Database($this->driver->getReflector(), isset($this->config['database']) ? $this->config['database'] : null);
|
|
|
+ return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -674,7 +610,7 @@ class Connection
|
|
|
*/
|
|
|
public function __wakeup()
|
|
|
{
|
|
|
- throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
|
|
|
+ throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -683,14 +619,14 @@ class Connection
|
|
|
*/
|
|
|
public function __sleep()
|
|
|
{
|
|
|
- throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
|
|
|
+ throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
|
|
}
|
|
|
|
|
|
|
|
|
- protected function onEvent($arg)
|
|
|
+ protected function onEvent($arg): void
|
|
|
{
|
|
|
- foreach ($this->onEvent ?: [] as $handler) {
|
|
|
- call_user_func($handler, $arg);
|
|
|
+ foreach ($this->onEvent as $handler) {
|
|
|
+ $handler($arg);
|
|
|
}
|
|
|
}
|
|
|
}
|