1
0
mirror of https://github.com/dg/dibi.git synced 2025-09-04 11:45:27 +02:00

Compare commits

..

28 Commits

Author SHA1 Message Date
David Grudl
86a71dde28 Released version 5.0.1 2023-11-25 14:08:47 +01:00
David Grudl
bb1f7d4b93 SqliteDriver: disables exceptions (is enabled since PHP 8.3) 2023-11-25 14:08:47 +01:00
David Grudl
680026747e added DibiExtension3 2023-11-25 14:08:47 +01:00
Jan Rössler
7ca47508cb PostgreReflector: detect IDENTITY columns as autoincrement 2023-11-05 20:38:26 +01:00
Jan Rössler
beba7b3592 PostgreReflector: fix autoincrement column detection 2023-11-05 20:38:26 +01:00
David Grudl
6cc7ce8e44 used PhpStorm Language attribute 2023-11-05 20:38:25 +01:00
Petr Hubík
92b8e6077e fix: PDO::errorInfo() can return NULL as a code, but Exception does not accept NULL code 2023-09-29 15:55:05 +02:00
David Grudl
e45638eab4 cs 2023-09-29 15:55:05 +02:00
Marek Bartoš
8564217bc1 Fluent: execute() has conditional return type 2023-09-05 12:40:51 +02:00
David Grudl
82c45c3076 Released version 5.0.0 2023-08-09 16:38:02 +02:00
Miloslav Hůla
df45bd3553 added object translators (#420)
The Translator is now capable to translate objects into Expression via object translators registered by the Connection.
2023-08-09 16:35:02 +02:00
Miloslav Hůla
fe22e230ce tests: remove dependency on dibi_test database name 2023-08-09 16:35:02 +02:00
David Grudl
8257532630 used native PHP 8 functions 2023-08-09 16:35:02 +02:00
David Grudl
08dfc37492 added PHP 8 typehints 2023-08-09 16:33:28 +02:00
David Grudl
7acef0c34b added property typehints 2023-08-09 16:33:28 +02:00
David Grudl
b01d97ac86 removed Dibi\Strict 2023-08-09 16:33:28 +02:00
David Grudl
8915b0343c removed support for PHP 7 2023-08-09 16:33:28 +02:00
David Grudl
d1a3362321 coding style 2023-08-09 16:33:28 +02:00
David Grudl
b6ead80202 composer: updated dependencies 2023-08-09 16:15:31 +02:00
David Grudl
a640ac2a8f requires PHP 8.0 2023-08-09 16:15:31 +02:00
David Grudl
87e702d1fc opened 5.0-dev 2023-08-09 16:15:31 +02:00
David Grudl
cb0cf4ba2f Released version 4.2.8 2023-08-09 16:15:07 +02:00
David Grudl
8e7df8374b drivers: removed auto-free feature 2023-08-09 16:15:07 +02:00
Marek Bartoš
848ac76fed Fluent: improved phpDoc 2023-08-09 16:15:07 +02:00
David Grudl
a0f2ca2fca typo 2023-08-09 16:15:04 +02:00
David Grudl
cf14987b42 cs 2023-08-05 19:56:38 +02:00
David Grudl
01c7ab63e3 tested in PHP 8.3 2023-08-05 19:50:57 +02:00
David Grudl
520119740d updated .gitattributes 2022-12-21 02:06:10 +01:00
71 changed files with 530 additions and 792 deletions

4
.gitattributes vendored
View File

@@ -1,8 +1,8 @@
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
.github export-ignore .github export-ignore
.travis.yml export-ignore appveyor.yml export-ignore
ecs.php export-ignore ncs.* export-ignore
phpstan.neon export-ignore phpstan.neon export-ignore
tests/ export-ignore tests/ export-ignore

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
php: ['8.0', '8.1', '8.2'] php: ['8.0', '8.1', '8.2', '8.3']
fail-fast: false fail-fast: false

View File

@@ -11,13 +11,14 @@
} }
], ],
"require": { "require": {
"php": ">=8.0 <8.3" "php": "8.0 - 8.3"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "^2.8", "tracy/tracy": "^2.9",
"nette/tester": "^2.4", "nette/tester": "^2.5",
"nette/di": "^3.0", "nette/di": "^3.1",
"phpstan/phpstan": "^0.12" "phpstan/phpstan": "^1.0",
"jetbrains/phpstorm-attributes": "^1.0"
}, },
"replace": { "replace": {
"dg/dibi": "*" "dg/dibi": "*"

View File

@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.2. The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3.
Usage Usage
@@ -341,7 +341,7 @@ $database->query('INSERT INTO users', [
There are three methods for dealing with transactions: There are three methods for dealing with transactions:
```php ```php
$database->beginTransaction(); $database->begin();
$database->commit(); $database->commit();
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon ```neon
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -20,7 +20,6 @@ use Tracy;
class DibiExtension22 extends Nette\DI\CompilerExtension class DibiExtension22 extends Nette\DI\CompilerExtension
{ {
private ?bool $debugMode; private ?bool $debugMode;
private ?bool $cliMode; private ?bool $cliMode;

View File

@@ -0,0 +1,96 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi\Bridges\Nette;
use Dibi;
use Nette;
use Nette\Schema\Expect;
use Tracy;
/**
* Dibi extension for Nette Framework 3. Creates 'connection' & 'panel' services.
*/
class DibiExtension3 extends Nette\DI\CompilerExtension
{
private ?bool $debugMode;
private ?bool $cliMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
}
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'autowired' => Expect::bool(true),
'flags' => Expect::anyOf(Expect::arrayOf('string'), Expect::type('dynamic')),
'profiler' => Expect::bool(),
'explain' => Expect::bool(true),
'filter' => Expect::bool(true),
'driver' => Expect::string()->dynamic(),
'name' => Expect::string()->dynamic(),
'lazy' => Expect::bool(false)->dynamic(),
'onConnect' => Expect::array()->dynamic(),
'substitutes' => Expect::arrayOf('string')->dynamic(),
'result' => Expect::structure([
'normalize' => Expect::bool(true),
'formatDateTime' => Expect::string(),
'formatTimeInterval' => Expect::string(),
'formatJson' => Expect::string(),
]),
])->otherItems(Expect::type('mixed'))
->castTo('array');
}
public function loadConfiguration()
{
$container = $this->getContainerBuilder();
$config = $this->getConfig();
$this->debugMode ??= $container->parameters['debugMode'];
$this->cliMode ??= $container->parameters['consoleMode'];
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
unset($config['profiler']);
if (is_array($config['flags'])) {
$flags = 0;
foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag);
}
$config['flags'] = $flags;
}
$connection = $container->addDefinition($this->prefix('connection'))
->setCreator(Dibi\Connection::class, [$config])
->setAutowired($config['autowired']);
if (class_exists(Tracy\Debugger::class)) {
$connection->addSetup(
[new Nette\DI\Definitions\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']],
);
}
if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel'))
->setCreator(Dibi\Bridges\Tracy\Panel::class, [
$config['explain'],
$config['filter'] ? Dibi\Event::QUERY : Dibi\Event::ALL,
]);
$connection->addSetup([$panel, 'register'], [$connection]);
}
}
}

View File

@@ -1,8 +1,8 @@
# This will create service named 'dibi.connection'. # This will create service named 'dibi.connection'.
# Requires Nette Framework 2.2 or later # Requires Nette Framework 3 or later
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -20,15 +20,9 @@ use Tracy;
*/ */
class Panel implements Tracy\IBarPanel class Panel implements Tracy\IBarPanel
{ {
use Dibi\Strict;
/** maximum SQL length */
public static int $maxLength = 1000; public static int $maxLength = 1000;
public bool|string $explain; public bool|string $explain;
public int $filter; public int $filter;
private array $events = []; private array $events = [];
@@ -68,7 +62,7 @@ class Panel implements Tracy\IBarPanel
if ($e instanceof Dibi\Exception && $e->getSql()) { if ($e instanceof Dibi\Exception && $e->getSql()) {
return [ return [
'tab' => 'SQL', 'tab' => 'SQL',
'panel' => Helpers::dump($e->getSql(), true), 'panel' => Helpers::dump($e->getSql(), return: true),
]; ];
} }
@@ -124,7 +118,7 @@ class Panel implements Tracy\IBarPanel
? $this->explain ? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN'); : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try { try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
} }
@@ -138,7 +132,7 @@ class Panel implements Tracy\IBarPanel
$s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>"; $s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>";
} }
$s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, true); $s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, return: true);
if ($explain) { if ($explain) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>"; $s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
} }
@@ -171,7 +165,7 @@ class Panel implements Tracy\IBarPanel
private function getConnectionName(Dibi\Connection $connection): string private function getConnectionName(Dibi\Connection $connection): string
{ {
$driver = $connection->getConfig('driver'); $driver = $connection->getConfig('driver');
return (is_object($driver) ? $driver::class : $driver) return get_debug_type($driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : ''); . ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
} }

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi; namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable; use Traversable;
@@ -20,26 +21,19 @@ use Traversable;
*/ */
class Connection implements IConnection class Connection implements IConnection
{ {
use Strict;
/** function (Event $event); Occurs after query is executed */ /** function (Event $event); Occurs after query is executed */
public ?array $onEvent = []; public ?array $onEvent = [];
/** Current connection configuration */
private array $config; private array $config;
/** @var string[] resultset formats */ /** @var string[] resultset formats */
private array $formats; private array $formats;
private ?Driver $driver = null; private ?Driver $driver = null;
private ?Translator $translator = null; private ?Translator $translator = null;
/** @var array<string, callable(object): Expression> */ /** @var array<string, callable(object): Expression | null> */
private array $translators = []; private array $translators = [];
private bool $sortTranslators = false;
private HashMap $substitutes; private HashMap $substitutes;
private int $transactionDepth = 0; private int $transactionDepth = 0;
@@ -216,7 +210,7 @@ class Connection implements IConnection
* Generates (translates) and executes SQL query. * Generates (translates) and executes SQL query.
* @throws Exception * @throws Exception
*/ */
final public function query(mixed ...$args): Result final public function query(#[Language('GenericSQL')] mixed ...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -226,7 +220,7 @@ class Connection implements IConnection
* Generates SQL query. * Generates SQL query.
* @throws Exception * @throws Exception
*/ */
final public function translate(mixed ...$args): string final public function translate(#[Language('GenericSQL')] mixed ...$args): string
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -239,7 +233,7 @@ class Connection implements IConnection
/** /**
* Generates and prints SQL query. * Generates and prints SQL query.
*/ */
final public function test(mixed ...$args): bool final public function test(#[Language('GenericSQL')] mixed ...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translate(...$args));
@@ -261,7 +255,7 @@ class Connection implements IConnection
* Generates (translates) and returns SQL query as DataSource. * Generates (translates) and returns SQL query as DataSource.
* @throws Exception * @throws Exception
*/ */
final public function dataSource(mixed ...$args): DataSource final public function dataSource(#[Language('GenericSQL')] mixed ...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translate(...$args), $this);
} }
@@ -271,7 +265,7 @@ class Connection implements IConnection
* Executes the SQL query. * Executes the SQL query.
* @throws Exception * @throws Exception
*/ */
final public function nativeQuery(string $sql): Result final public function nativeQuery(#[Language('SQL')] string $sql): Result
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -528,19 +522,68 @@ class Connection implements IConnection
/********************* value objects translation ****************d*g**/ /********************* value objects translation ****************d*g**/
/** @param callable(object): Expression $translator */ /**
public function addObjectTranslator(string $class, callable $translator): self * @param callable(object): Expression $translator
*/
public function setObjectTranslator(callable $translator): void
{ {
$this->translators[$class] = $translator; if (!$translator instanceof \Closure) {
uksort($this->translators, fn($a, $b) => class_exists($a, false) && is_subclass_of($a, $b) ? -1 : 1); $translator = \Closure::fromCallable($translator);
return $this; }
$param = (new \ReflectionFunction($translator))->getParameters()[0] ?? null;
$type = $param?->getType();
$types = match (true) {
$type instanceof \ReflectionNamedType => [$type],
$type instanceof \ReflectionUnionType => $type->getTypes(),
default => throw new Exception('Object translator must have exactly one parameter with class typehint.'),
};
foreach ($types as $type) {
if ($type->isBuiltin() || $type->allowsNull()) {
throw new Exception("Object translator must have exactly one parameter with non-nullable class typehint, got '$type'.");
}
$this->translators[$type->getName()] = $translator;
}
$this->sortTranslators = true;
} }
/** @return array<string, callable(object): Expression> */ public function translateObject(object $object): ?Expression
public function getObjectTranslators(): array
{ {
return $this->translators; if ($this->sortTranslators) {
$this->translators = array_filter($this->translators);
uksort($this->translators, fn($a, $b) => is_subclass_of($a, $b) ? -1 : 1);
$this->sortTranslators = false;
}
if (!array_key_exists($object::class, $this->translators)) {
$translator = null;
foreach ($this->translators as $class => $t) {
if ($object instanceof $class) {
$translator = $t;
break;
}
}
$this->translators[$object::class] = $translator;
}
$translator = $this->translators[$object::class];
if ($translator === null) {
return null;
}
$result = $translator($object);
if (!$result instanceof Expression) {
throw new Exception(sprintf(
"Object translator for class '%s' returned '%s' but %s expected.",
$object::class,
get_debug_type($result),
Expression::class,
));
}
return $result;
} }
@@ -551,7 +594,7 @@ class Connection implements IConnection
* Executes SQL query and fetch result - shortcut for query() & fetch(). * Executes SQL query and fetch result - shortcut for query() & fetch().
* @throws Exception * @throws Exception
*/ */
public function fetch(mixed ...$args): ?Row public function fetch(#[Language('GenericSQL')] mixed ...$args): ?Row
{ {
return $this->query($args)->fetch(); return $this->query($args)->fetch();
} }
@@ -562,7 +605,7 @@ class Connection implements IConnection
* @return Row[]|array[] * @return Row[]|array[]
* @throws Exception * @throws Exception
*/ */
public function fetchAll(mixed ...$args): array public function fetchAll(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchAll(); return $this->query($args)->fetchAll();
} }
@@ -572,7 +615,7 @@ class Connection implements IConnection
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle(). * Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @throws Exception * @throws Exception
*/ */
public function fetchSingle(mixed ...$args): mixed public function fetchSingle(#[Language('GenericSQL')] mixed ...$args): mixed
{ {
return $this->query($args)->fetchSingle(); return $this->query($args)->fetchSingle();
} }
@@ -582,7 +625,7 @@ class Connection implements IConnection
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs(). * Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @throws Exception * @throws Exception
*/ */
public function fetchPairs(mixed ...$args): array public function fetchPairs(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchPairs(); return $this->query($args)->fetchPairs();
} }

View File

@@ -15,26 +15,15 @@ namespace Dibi;
*/ */
class DataSource implements IDataSource class DataSource implements IDataSource
{ {
use Strict;
private Connection $connection; private Connection $connection;
private string $sql; private string $sql;
private ?Result $result = null; private ?Result $result = null;
private ?int $count = null; private ?int $count = null;
private ?int $totalCount = null; private ?int $totalCount = null;
private array $cols = []; private array $cols = [];
private array $sorting = []; private array $sorting = [];
private array $conds = []; private array $conds = [];
private ?int $offset = null; private ?int $offset = null;
private ?int $limit = null; private ?int $limit = null;

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/ */
class DateTime extends \DateTimeImmutable class DateTime extends \DateTimeImmutable
{ {
use Strict;
public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null) public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null)
{ {
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get()); $timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
{ {
use Dibi\Strict;
public function disconnect(): void public function disconnect(): void
{ {
} }

View File

@@ -26,8 +26,6 @@ use Dibi\Helpers;
*/ */
class FirebirdDriver implements Dibi\Driver class FirebirdDriver implements Dibi\Driver
{ {
use Dibi\Strict;
public const ERROR_EXCEPTION_THROWN = -836; public const ERROR_EXCEPTION_THROWN = -836;
/** @var resource */ /** @var resource */
@@ -35,7 +33,6 @@ class FirebirdDriver implements Dibi\Driver
/** @var ?resource */ /** @var ?resource */
private $transaction; private $transaction;
private bool $inTransaction = false; private bool $inTransaction = false;

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class FirebirdReflector implements Dibi\Reflector class FirebirdReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -18,13 +18,9 @@ use Dibi\Helpers;
*/ */
class FirebirdResult implements Dibi\ResultDriver class FirebirdResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private bool $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -35,17 +31,6 @@ class FirebirdResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -104,7 +89,6 @@ class FirebirdResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): mixed public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -18,8 +18,6 @@ use Dibi;
*/ */
class MySqlReflector implements Dibi\Reflector class MySqlReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -32,17 +32,11 @@ use Dibi;
*/ */
class MySqliDriver implements Dibi\Driver class MySqliDriver implements Dibi\Driver
{ {
use Dibi\Strict;
public const ERROR_ACCESS_DENIED = 1045; public const ERROR_ACCESS_DENIED = 1045;
public const ERROR_DUPLICATE_ENTRY = 1062; public const ERROR_DUPLICATE_ENTRY = 1062;
public const ERROR_DATA_TRUNCATED = 1265; public const ERROR_DATA_TRUNCATED = 1265;
private \mysqli $connection; private \mysqli $connection;
/** Is buffered (seekable and countable)? */
private bool $buffered = false; private bool $buffered = false;

View File

@@ -17,13 +17,7 @@ use Dibi;
*/ */
class MySqliResult implements Dibi\ResultDriver class MySqliResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
private \mysqli_result $resultSet; private \mysqli_result $resultSet;
private bool $autoFree = true;
/** Is buffered (seekable and countable)? */
private bool $buffered; private bool $buffered;
@@ -34,17 +28,6 @@ class MySqliResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -134,7 +117,6 @@ class MySqliResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \mysqli_result public function getResultResource(): \mysqli_result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class NoDataResult implements Dibi\ResultDriver class NoDataResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
private int $rows; private int $rows;

View File

@@ -25,13 +25,9 @@ use Dibi;
*/ */
class OdbcDriver implements Dibi\Driver class OdbcDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private ?int $affectedRows; private ?int $affectedRows;
private bool $microseconds = true; private bool $microseconds = true;
@@ -123,7 +119,7 @@ class OdbcDriver implements Dibi\Driver
*/ */
public function begin(?string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if (!odbc_autocommit($this->connection, false)) { if (!odbc_autocommit($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class OdbcReflector implements Dibi\Reflector class OdbcReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -17,13 +17,8 @@ use Dibi;
*/ */
class OdbcResult implements Dibi\ResultDriver class OdbcResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private bool $autoFree = true;
private int $row = 0; private int $row = 0;
@@ -36,17 +31,6 @@ class OdbcResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -127,7 +111,6 @@ class OdbcResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): mixed public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -27,16 +27,10 @@ use Dibi;
*/ */
class OracleDriver implements Dibi\Driver class OracleDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private bool $autocommit = true; private bool $autocommit = true;
/** use native datetime format */
private bool $nativeDate; private bool $nativeDate;
private ?int $affectedRows; private ?int $affectedRows;

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class OracleReflector implements Dibi\Reflector class OracleReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -17,13 +17,9 @@ use Dibi;
*/ */
class OracleResult implements Dibi\ResultDriver class OracleResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private bool $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -34,17 +30,6 @@ class OracleResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -110,7 +95,6 @@ class OracleResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): mixed public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -27,14 +27,9 @@ use PDO;
*/ */
class PdoDriver implements Dibi\Driver class PdoDriver implements Dibi\Driver
{ {
use Dibi\Strict;
private ?PDO $connection; private ?PDO $connection;
private ?int $affectedRows; private ?int $affectedRows;
private string $driverName; private string $driverName;
private string $serverVersion = ''; private string $serverVersion = '';
@@ -98,23 +93,15 @@ class PdoDriver implements Dibi\Driver
$this->affectedRows = null; $this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo(); [$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message"; $message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) { throw match ($this->driverName) {
case 'mysql': 'mysql' => MySqliDriver::createException($message, $code, $sql),
throw MySqliDriver::createException($message, $code, $sql); 'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
case 'oci': 'sqlite' => SqliteDriver::createException($message, $code, $sql),
throw OracleDriver::createException($message, $code, $sql); default => new Dibi\DriverException($message, $code, $sql),
};
case 'pgsql':
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw SqliteDriver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
}
} }
@@ -144,7 +131,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -157,7 +144,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -170,7 +157,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }

View File

@@ -19,10 +19,7 @@ use PDO;
*/ */
class PdoResult implements Dibi\ResultDriver class PdoResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
private ?\PDOStatement $resultSet; private ?\PDOStatement $resultSet;
private string $driverName; private string $driverName;

View File

@@ -28,11 +28,8 @@ use PgSql;
*/ */
class PostgreDriver implements Dibi\Driver class PostgreDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource|PgSql\Connection */ /** @var resource|PgSql\Connection */
private $connection; private $connection;
private ?int $affectedRows; private ?int $affectedRows;

View File

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class PostgreReflector implements Dibi\Reflector class PostgreReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;
private string $version; private string $version;
@@ -75,13 +72,6 @@ class PostgreReflector implements Dibi\Reflector
public function getColumns(string $table): array public function getColumns(string $table): array
{ {
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table)); $_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$res = $this->driver->query("
SELECT indkey
FROM pg_class
LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
WHERE pg_class.oid = $_table::regclass
");
$primary = (int) $res->fetch(true)['indkey'];
$res = $this->driver->query(" $res = $this->driver->query("
SELECT * SELECT *
@@ -101,7 +91,8 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
pg_get_expr(adef.adbin, adef.adrelid) AS column_default pg_get_expr(adef.adbin, adef.adrelid) AS column_default,
CASE WHEN a.attidentity IN ('a', 'd') THEN 'YES' ELSE 'NO' END AS is_identity
FROM FROM
pg_attribute a pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid JOIN pg_type ON a.atttypid = pg_type.oid
@@ -126,7 +117,7 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null, 'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true, 'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'default' => $row['column_default'], 'default' => $row['column_default'],
'autoincrement' => (int) $row['ordinal_position'] === $primary && str_starts_with($row['column_default'] ?? '', 'nextval'), 'autoincrement' => $row['is_identity'] === 'YES' || str_starts_with($row['column_default'] ?? '', 'nextval('),
'vendor' => $row, 'vendor' => $row,
]; ];
} }

View File

@@ -19,13 +19,9 @@ use PgSql;
*/ */
class PostgreResult implements Dibi\ResultDriver class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource|PgSql\Result */ /** @var resource|PgSql\Result */
private $resultSet; private $resultSet;
private bool $autoFree = true;
/** /**
* @param resource|PgSql\Result $resultSet * @param resource|PgSql\Result $resultSet
@@ -36,17 +32,6 @@ class PostgreResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -113,7 +98,6 @@ class PostgreResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): mixed public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet ? $this->resultSet
: null; : null;

View File

@@ -25,12 +25,8 @@ use SQLite3;
*/ */
class SqliteDriver implements Dibi\Driver class SqliteDriver implements Dibi\Driver
{ {
use Dibi\Strict;
private SQLite3 $connection; private SQLite3 $connection;
private string $fmtDate; private string $fmtDate;
private string $fmtDateTime; private string $fmtDateTime;
@@ -61,6 +57,7 @@ class SqliteDriver implements Dibi\Driver
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced) // enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
$version = SQLite3::version(); $version = SQLite3::version();
$this->connection->enableExceptions(false);
if ($version['versionNumber'] >= '3006019') { if ($version['versionNumber'] >= '3006019') {
$this->query('PRAGMA foreign_keys = ON'); $this->query('PRAGMA foreign_keys = ON');
} }

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class SqliteReflector implements Dibi\Reflector class SqliteReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -18,12 +18,8 @@ use Dibi\Helpers;
*/ */
class SqliteResult implements Dibi\ResultDriver class SqliteResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
private \SQLite3Result $resultSet; private \SQLite3Result $resultSet;
private bool $autoFree = true;
public function __construct(\SQLite3Result $resultSet) public function __construct(\SQLite3Result $resultSet)
{ {
@@ -31,17 +27,6 @@ class SqliteResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
* @throws Dibi\NotSupportedException * @throws Dibi\NotSupportedException
@@ -107,7 +92,6 @@ class SqliteResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \SQLite3Result public function getResultResource(): \SQLite3Result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

@@ -27,13 +27,9 @@ use Dibi\Helpers;
*/ */
class SqlsrvDriver implements Dibi\Driver class SqlsrvDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private ?int $affectedRows; private ?int $affectedRows;
private string $version = ''; private string $version = '';

View File

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class SqlsrvReflector implements Dibi\Reflector class SqlsrvReflector implements Dibi\Reflector
{ {
use Dibi\Strict;
private Dibi\Driver $driver; private Dibi\Driver $driver;

View File

@@ -17,13 +17,9 @@ use Dibi;
*/ */
class SqlsrvResult implements Dibi\ResultDriver class SqlsrvResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private bool $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -34,17 +30,6 @@ class SqlsrvResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -106,7 +91,6 @@ class SqlsrvResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): mixed public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/ */
class Event class Event
{ {
use Strict;
/** event type */ /** event type */
public const public const
CONNECT = 1, CONNECT = 1,
@@ -32,17 +30,11 @@ class Event
ALL = 1023; ALL = 1023;
public Connection $connection; public Connection $connection;
public int $type; public int $type;
public string $sql; public string $sql;
public Result|DriverException|null $result; public Result|DriverException|null $result;
public float $time; public float $time;
public ?int $count = null; public ?int $count = null;
public ?array $source = null; public ?array $source = null;

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/ */
class Expression class Expression
{ {
use Strict;
private array $values; private array $values;

View File

@@ -27,6 +27,8 @@ namespace Dibi;
* @method Fluent innerJoin(...$table) * @method Fluent innerJoin(...$table)
* @method Fluent rightJoin(...$table) * @method Fluent rightJoin(...$table)
* @method Fluent outerJoin(...$table) * @method Fluent outerJoin(...$table)
* @method Fluent union(Fluent $fluent)
* @method Fluent unionAll(Fluent $fluent)
* @method Fluent as(...$field) * @method Fluent as(...$field)
* @method Fluent on(...$cond) * @method Fluent on(...$cond)
* @method Fluent and(...$cond) * @method Fluent and(...$cond)
@@ -43,8 +45,6 @@ namespace Dibi;
*/ */
class Fluent implements IDataSource class Fluent implements IDataSource
{ {
use Strict;
public const REMOVE = false; public const REMOVE = false;
public static array $masks = [ public static array $masks = [
@@ -92,15 +92,10 @@ class Fluent implements IDataSource
]; ];
private Connection $connection; private Connection $connection;
private array $setups = []; private array $setups = [];
private ?string $command = null; private ?string $command = null;
private array $clauses = []; private array $clauses = [];
private array $flags = []; private array $flags = [];
private $cursor; private $cursor;
/** normalized clauses */ /** normalized clauses */
@@ -284,19 +279,17 @@ class Fluent implements IDataSource
/** /**
* Generates and executes SQL query. * Generates and executes SQL query.
* Returns result set or number of affected rows * Returns result set or number of affected rows
* @return ($return is \dibi::IDENTIFIER|\dibi::AFFECTED_ROWS ? int : Result)
* @throws Exception * @throws Exception
*/ */
public function execute(?string $return = null): Result|int|null public function execute(?string $return = null): Result|int|null
{ {
$res = $this->query($this->_export()); $res = $this->query($this->_export());
switch ($return) { return match ($return) {
case \dibi::IDENTIFIER: \dibi::IDENTIFIER => $this->connection->getInsertId(),
return $this->connection->getInsertId(); \dibi::AFFECTED_ROWS => $this->connection->getAffectedRows(),
case \dibi::AFFECTED_ROWS: default => $res,
return $this->connection->getAffectedRows(); };
default:
return $res;
}
} }

View File

@@ -46,7 +46,7 @@ abstract class HashMapBase
*/ */
final class HashMap extends HashMapBase final class HashMap extends HashMapBase
{ {
public function __set(string $nm, $val) public function __set(string $nm, mixed $val): void
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
@@ -56,7 +56,7 @@ final class HashMap extends HashMapBase
} }
public function __get(string $nm) public function __get(string $nm): mixed
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";

View File

@@ -12,8 +12,6 @@ namespace Dibi;
class Helpers class Helpers
{ {
use Strict;
private static HashMap $types; private static HashMap $types;

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/ */
class Literal class Literal
{ {
use Strict;
private string $value; private string $value;

View File

@@ -17,13 +17,9 @@ use Dibi;
*/ */
class FileLogger class FileLogger
{ {
use Dibi\Strict;
/** Name of the file where SQL errors should be logged */ /** Name of the file where SQL errors should be logged */
public string $file; public string $file;
public int $filter; public int $filter;
private bool $errorsOnly; private bool $errorsOnly;
@@ -74,7 +70,7 @@ class FileLogger
{ {
$driver = $event->connection->getConfig('driver'); $driver = $event->connection->getConfig('driver');
$message .= $message .=
"\n-- driver: " . (is_object($driver) ? $driver::class : $driver) . '/' . $event->connection->getConfig('name') "\n-- driver: " . get_debug_type($driver) . '/' . $event->connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s') . "\n-- " . date('Y-m-d H:i:s')
. "\n\n"; . "\n\n";
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX); file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);

View File

@@ -27,8 +27,6 @@ use Dibi;
*/ */
class Column class Column
{ {
use Dibi\Strict;
/** when created by Result */ /** when created by Result */
private ?Dibi\Reflector $reflector; private ?Dibi\Reflector $reflector;

View File

@@ -21,10 +21,7 @@ use Dibi;
*/ */
class Database class Database
{ {
use Dibi\Strict;
private Dibi\Reflector $reflector; private Dibi\Reflector $reflector;
private ?string $name; private ?string $name;
/** @var Table[] */ /** @var Table[] */

View File

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection; namespace Dibi\Reflection;
use Dibi;
/** /**
@@ -20,8 +19,6 @@ use Dibi;
*/ */
class ForeignKey class ForeignKey
{ {
use Dibi\Strict;
private string $name; private string $name;
/** @var array of [local, foreign, onDelete, onUpdate] */ /** @var array of [local, foreign, onDelete, onUpdate] */

View File

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection; namespace Dibi\Reflection;
use Dibi;
/** /**
@@ -22,8 +21,6 @@ use Dibi;
*/ */
class Index class Index
{ {
use Dibi\Strict;
/** @var array (name, columns, [unique], [primary]) */ /** @var array (name, columns, [unique], [primary]) */
private array $info; private array $info;

View File

@@ -20,8 +20,6 @@ use Dibi;
*/ */
class Result class Result
{ {
use Dibi\Strict;
private Dibi\ResultDriver $driver; private Dibi\ResultDriver $driver;
/** @var Column[]|null */ /** @var Column[]|null */

View File

@@ -25,12 +25,8 @@ use Dibi;
*/ */
class Table class Table
{ {
use Dibi\Strict;
private Dibi\Reflector $reflector; private Dibi\Reflector $reflector;
private string $name; private string $name;
private bool $view; private bool $view;
/** @var Column[] */ /** @var Column[] */
@@ -41,7 +37,6 @@ class Table
/** @var Index[] */ /** @var Index[] */
private array $indexes; private array $indexes;
private ?Index $primaryKey; private ?Index $primaryKey;

View File

@@ -17,13 +17,10 @@ namespace Dibi;
*/ */
class Result implements IDataSource class Result implements IDataSource
{ {
use Strict;
private ?ResultDriver $driver; private ?ResultDriver $driver;
/** Translate table */ /** Translate table */
private array $types = []; private array $types = [];
private ?Reflection\Result $meta; private ?Reflection\Result $meta;
/** Already fetched? Used for allowance for first seek(0) */ /** Already fetched? Used for allowance for first seek(0) */
@@ -34,7 +31,6 @@ class Result implements IDataSource
/** @var callable|null returned object factory */ /** @var callable|null returned object factory */
private $rowFactory; private $rowFactory;
private array $formats = []; private array $formats = [];

View File

@@ -15,12 +15,8 @@ namespace Dibi;
*/ */
class ResultIterator implements \Iterator, \Countable class ResultIterator implements \Iterator, \Countable
{ {
use Strict;
private Result $result; private Result $result;
private mixed $row; private mixed $row;
private int $pointer = 0; private int $pointer = 0;
@@ -44,6 +40,7 @@ class ResultIterator implements \Iterator, \Countable
/** /**
* Returns the key of the current element. * Returns the key of the current element.
*/ */
#[\ReturnTypeWillChange]
public function key(): mixed public function key(): mixed
{ {
return $this->pointer; return $this->pointer;
@@ -53,6 +50,7 @@ class ResultIterator implements \Iterator, \Countable
/** /**
* Returns the current element. * Returns the current element.
*/ */
#[\ReturnTypeWillChange]
public function current(): mixed public function current(): mixed
{ {
return $this->row; return $this->row;

View File

@@ -48,10 +48,11 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
public function __get(string $key) public function __get(string $key): mixed
{ {
$hint = Helpers::getSuggestion(array_keys((array) $this), $key); $hint = Helpers::getSuggestion(array_keys((array) $this), $key);
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE); trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
return null;
} }

View File

@@ -1,112 +0,0 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Better OOP experience.
*/
trait Strict
{
/** @var array [method => [type => callback]] */
private static array $extMethods;
/**
* Call to undefined method.
* @throws \LogicException
*/
public function __call(string $name, array $args)
{
$class = method_exists($this, $name) ? 'parent' : static::class;
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined method $class::$name()$hint");
}
/**
* Call to undefined static method.
* @throws \LogicException
*/
public static function __callStatic(string $name, array $args)
{
$rc = new ReflectionClass(static::class);
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), fn($m) => $m->isPublic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function &__get(string $name)
{
if ((method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name))
&& (new ReflectionMethod($this, $m))->isPublic()
) { // back compatiblity
$ret = $this->$m();
return $ret;
}
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __set(string $name, $value)
{
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
}
public function __isset(string $name): bool
{
return false;
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __unset(string $name)
{
$class = static::class;
throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
}
}

View File

@@ -15,29 +15,18 @@ namespace Dibi;
*/ */
final class Translator final class Translator
{ {
use Strict;
private Connection $connection; private Connection $connection;
private Driver $driver; private Driver $driver;
private int $cursor = 0; private int $cursor = 0;
private array $args; private array $args;
/** @var string[] */ /** @var string[] */
private array $errors; private array $errors;
private bool $comment = false; private bool $comment = false;
private int $ifLevel = 0; private int $ifLevel = 0;
private int $ifLevelStart = 0; private int $ifLevelStart = 0;
private ?int $limit = null; private ?int $limit = null;
private ?int $offset = null; private ?int $offset = null;
private HashMap $identifiers; private HashMap $identifiers;
@@ -268,7 +257,7 @@ final class Translator
$proto = array_keys($v); $proto = array_keys($v);
} }
} else { } else {
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? $v::class : gettype($v)) . '**'; return $this->errors[] = '**Unexpected type ' . get_debug_type($v) . '**';
} }
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
@@ -318,13 +307,9 @@ final class Translator
&& $modifier === null && $modifier === null
&& !$value instanceof Literal && !$value instanceof Literal
&& !$value instanceof Expression && !$value instanceof Expression
&& $result = $this->connection->translateObject($value)
) { ) {
foreach ($this->connection->getObjectTranslators() as $class => $translator) { return $this->connection->translate(...$result->getValues());
if ($value instanceof $class) {
$value = $translator($value);
return $this->connection->translate(...$value->getValues());
}
}
} }
// object-to-scalar procession // object-to-scalar procession
@@ -350,7 +335,7 @@ final class Translator
) { ) {
// continue // continue
} else { } else {
$type = is_object($value) ? $value::class : gettype($value); $type = get_debug_type($value);
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**"; return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
} }
} }
@@ -438,18 +423,17 @@ final class Translator
$value = substr($value, 0, $toSkip) $value = substr($value, 0, $toSkip)
. preg_replace_callback( . preg_replace_callback(
<<<'XX' <<<'XX'
/ /
(?=[`['":]) (?=[`['":])
(?: (?:
`(.+?)`| `(.+?)`|
\[(.+?)\]| \[(.+?)]|
(')((?:''|[^'])*)'| (')((?:''|[^'])*)'|
(")((?:""|[^"])*)"| (")((?:""|[^"])*)"|
('|")| (['"])|
:(\S*?:)([a-zA-Z0-9._]?) :(\S*?:)([a-zA-Z0-9._]?)
)/sx )/sx
XX XX,
,
[$this, 'cb'], [$this, 'cb'],
substr($value, $toSkip), substr($value, $toSkip),
); );
@@ -517,7 +501,7 @@ final class Translator
return $this->connection->translate(...$value->getValues()); return $this->connection->translate(...$value->getValues());
} else { } else {
$type = is_object($value) ? $value::class : gettype($value); $type = get_debug_type($value);
return $this->errors[] = "**Unexpected $type**"; return $this->errors[] = "**Unexpected $type**";
} }
} }

View File

@@ -37,14 +37,12 @@ declare(strict_types=1);
*/ */
class dibi class dibi
{ {
use Dibi\Strict;
public const public const
AFFECTED_ROWS = 'a', AFFECTED_ROWS = 'a',
IDENTIFIER = 'n'; IDENTIFIER = 'n';
/** version */ /** version */
public const VERSION = '5.0-dev'; public const VERSION = '5.0.1';
/** sorting order */ /** sorting order */
public const public const

View File

@@ -73,24 +73,29 @@ test('', function () use ($config) {
test('', function () use ($config) { test('', function () use ($config) {
Assert::exception(function () use ($config) { Assert::exception(
new Connection($config + ['onConnect' => '']); fn() => new Connection($config + ['onConnect' => '']),
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array."); InvalidArgumentException::class,
"Configuration option 'onConnect' must be array.",
);
$e = Assert::exception(function () use ($config) { $e = Assert::exception(
new Connection($config + ['onConnect' => ['STOP']]); fn() => new Connection($config + ['onConnect' => ['STOP']]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP', $e->getSql()); Assert::same('STOP', $e->getSql());
$e = Assert::exception(function () use ($config) { $e = Assert::exception(
new Connection($config + ['onConnect' => [['STOP %i', 123]]]); fn() => new Connection($config + ['onConnect' => [['STOP %i', 123]]]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP 123', $e->getSql()); Assert::same('STOP 123', $e->getSql());
// lazy // lazy
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]); $conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT 1'); fn() => $conn->query('SELECT 1'),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP', $e->getSql()); Assert::same('STOP', $e->getSql());
}); });

View File

@@ -24,14 +24,16 @@ class Time extends DateTimeImmutable
test('Without object translator', function () use ($conn) { test('Without object translator', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('?', new Email); fn() => $conn->translate('?', new Email),
}, Dibi\Exception::class, 'SQL translate error: Unexpected Email'); Dibi\Exception::class,
'SQL translate error: Unexpected Email',
);
}); });
test('Basics', function () use ($conn) { test('Basics', function () use ($conn) {
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address)); $conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
Assert::same( Assert::same(
reformat([ reformat([
'sqlsrv' => "N'address@example.com'", 'sqlsrv' => "N'address@example.com'",
@@ -53,7 +55,7 @@ test('DateTime', function () use ($conn) {
// With object translator // With object translator
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s'))); $conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
Assert::same( Assert::same(
reformat([ reformat([
'sqlsrv' => "OwnTime(N'12:13:14')", 'sqlsrv' => "OwnTime(N'12:13:14')",
@@ -86,7 +88,7 @@ test('DateTime', function () use ($conn) {
); );
// But DateTime translation can be overloaded // But DateTime translation can be overloaded
$conn->addObjectTranslator(DateTimeInterface::class, fn() => new Dibi\Expression('OwnDateTime')); $conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
Assert::same( Assert::same(
'OwnDateTime', 'OwnDateTime',
$conn->translate('?', $dt), $conn->translate('?', $dt),
@@ -95,9 +97,9 @@ test('DateTime', function () use ($conn) {
test('Complex structures', function () use ($conn) { test('Complex structures', function () use ($conn) {
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address)); $conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s'))); $conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s'))); $conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14'); $time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same( Assert::same(
@@ -117,3 +119,37 @@ test('Complex structures', function () use ($conn) {
]), ]),
); );
}); });
test('Invalid translator', function () use ($conn) {
Assert::exception(
fn() => $conn->setObjectTranslator(fn($email) => 'foo'),
Dibi\Exception::class,
'Object translator must have exactly one parameter with class typehint.',
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(string $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got 'string'.",
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|bool $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got 'bool'.",
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|null $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got '?Email'.",
);
$conn->setObjectTranslator(fn(Email $email) => 'foo');
Assert::exception(
fn() => $conn->translate('?', new Email),
Dibi\Exception::class,
"Object translator for class 'Email' returned 'string' but Dibi\\Expression expected.",
);
});

View File

@@ -15,18 +15,22 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
/*Assert::exception(function () use ($conn) { /*
$conn->rollback(); Assert::exception(
}, Dibi\Exception::class); fn() => $conn->rollback(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->commit(); fn() => $conn->commit(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
$conn->begin(); $conn->begin();
Assert::exception(function () use ($conn) { Assert::exception(
$conn->begin(); fn() => $conn->begin(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
*/ */
@@ -53,14 +57,16 @@ test('begin() & commit()', function () use ($conn) {
test('transaction() fail', function () use ($conn) { test('transaction() fail', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [ $connection->query('INSERT INTO [products]', [
'title' => 'Test product', 'title' => 'Test product',
]); ]);
throw new Exception('my exception'); throw new Exception('my exception');
}); }),
}, Throwable::class, 'my exception'); Throwable::class,
'my exception',
);
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
}); });
@@ -76,8 +82,8 @@ test('transaction() success', function () use ($conn) {
test('nested transaction() call fail', function () use ($conn) { test('nested transaction() call fail', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [ $connection->query('INSERT INTO [products]', [
'title' => 'Test product', 'title' => 'Test product',
]); ]);
@@ -88,8 +94,10 @@ test('nested transaction() call fail', function () use ($conn) {
]); ]);
throw new Exception('my exception'); throw new Exception('my exception');
}); });
}); }),
}, Throwable::class, 'my exception'); Throwable::class,
'my exception',
);
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
}); });
@@ -111,21 +119,27 @@ test('nested transaction() call success', function () use ($conn) {
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) { test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->begin(); $connection->begin();
}); }),
}, LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback'); LogicException::class,
Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback',
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->commit(); $connection->commit();
}); }),
}, LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback'); LogicException::class,
Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback',
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->rollback(); $connection->rollback();
}); }),
}, LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback'); LogicException::class,
Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback',
);
}); });

View File

@@ -147,9 +147,11 @@ if ($config['system'] === 'mysql') {
->limit(' 1; DROP TABLE users') ->limit(' 1; DROP TABLE users')
->offset(' 1; DROP TABLE users'); ->offset(' 1; DROP TABLE users');
Assert::exception(function () use ($fluent) { Assert::exception(
(string) $fluent; fn() => (string) $fluent,
}, Dibi\Exception::class, "Expected number, ' 1; DROP TABLE users' given."); Dibi\Exception::class,
"Expected number, ' 1; DROP TABLE users' given.",
);
} }

View File

@@ -12,22 +12,32 @@ Assert::same(0, Helpers::intVal(0));
Assert::same(0, Helpers::intVal('0')); Assert::same(0, Helpers::intVal('0'));
Assert::same(-10, Helpers::intVal('-10')); Assert::same(-10, Helpers::intVal('-10'));
Assert::exception(function () { Assert::exception(
Helpers::intVal('12345678901234567890123456879'); fn() => Helpers::intVal('12345678901234567890123456879'),
}, Dibi\Exception::class, 'Number 12345678901234567890123456879 is greater than integer.'); Dibi\Exception::class,
'Number 12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () { Assert::exception(
Helpers::intVal('-12345678901234567890123456879'); fn() => Helpers::intVal('-12345678901234567890123456879'),
}, Dibi\Exception::class, 'Number -12345678901234567890123456879 is greater than integer.'); Dibi\Exception::class,
'Number -12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () { Assert::exception(
Helpers::intVal(''); fn() => Helpers::intVal(''),
}, Dibi\Exception::class, "Expected number, '' given."); Dibi\Exception::class,
"Expected number, '' given.",
);
Assert::exception(function () { Assert::exception(
Helpers::intVal('not number'); fn() => Helpers::intVal('not number'),
}, Dibi\Exception::class, "Expected number, 'not number' given."); Dibi\Exception::class,
"Expected number, 'not number' given.",
);
Assert::exception(function () { Assert::exception(
Helpers::intVal(null); fn() => Helpers::intVal(null),
}, Dibi\Exception::class, "Expected number, '' given."); Dibi\Exception::class,
"Expected number, '' given.",
);

View File

@@ -19,17 +19,22 @@ function buildPdoDriver(?int $errorMode)
// PDO error mode: exception // PDO error mode: exception
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_EXCEPTION); fn() => buildPdoDriver(PDO::ERRMODE_EXCEPTION),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
// PDO error mode: warning // PDO error mode: warning
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_WARNING); fn() => buildPdoDriver(PDO::ERRMODE_WARNING),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
test('PDO error mode: explicitly set silent', function () { test(
buildPdoDriver(PDO::ERRMODE_SILENT); 'PDO error mode: explicitly set silent',
}); fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
);

View File

@@ -162,9 +162,10 @@ test('', function () {
if (PHP_VERSION_ID < 80000) { if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1 Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else { } else {
Assert::exception(function () use ($result) { Assert::exception(
Assert::same(['col' => 0], $result->test(['col' => ''])); fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
}, TypeError::class); TypeError::class,
);
} }
Assert::same(['col' => 0], $result->test(['col' => '0'])); Assert::same(['col' => 0], $result->test(['col' => '0']));
@@ -189,9 +190,10 @@ test('', function () {
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DATETIME);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -208,9 +210,10 @@ test('', function () {
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -226,9 +229,10 @@ test('', function () {
$result->setType('col', Type::DATE); $result->setType('col', Type::DATE);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -242,9 +246,10 @@ test('', function () {
$result->setType('col', Type::TIME); $result->setType('col', Type::TIME);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));

View File

@@ -24,13 +24,17 @@ Assert::true(isset($row['title']));
// missing // missing
Assert::error(function () use ($row) { Assert::error(
$x = $row->missing; fn() => $x = $row->missing,
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['missing']; fn() => $x = $row['missing'],
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::false(isset($row->missing)); Assert::false(isset($row->missing));
Assert::false(isset($row['missing'])); Assert::false(isset($row['missing']));
@@ -41,13 +45,17 @@ Assert::same(123, $row['missing'] ?? 123);
// suggestions // suggestions
Assert::error(function () use ($row) { Assert::error(
$x = $row->tilte; fn() => $x = $row->tilte,
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['tilte']; fn() => $x = $row['tilte'],
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
// to array // to array

View File

@@ -83,7 +83,7 @@ $tests = function ($conn) {
); );
Assert::exception( Assert::exception(
$conn->translate('SELECT 1 %ofs %lmt', 10, 10), fn() => $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
Dibi\NotSupportedException::class, Dibi\NotSupportedException::class,
); );
} }

View File

@@ -1,151 +0,0 @@
<?php
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
class TestClass
{
use Dibi\Strict;
public $public;
public static $publicStatic;
protected $protected;
public function publicMethod()
{
}
public static function publicMethodStatic()
{
}
protected function protectedMethod()
{
}
protected static function protectedMethodS()
{
}
public function getBar()
{
return 123;
}
public function isFoo()
{
return 456;
}
}
class TestChild extends TestClass
{
public function callParent()
{
parent::callParent();
}
}
// calling
Assert::exception(function () {
$obj = new TestClass;
$obj->undeclared();
}, LogicException::class, 'Call to undefined method TestClass::undeclared().');
Assert::exception(function () {
TestClass::undeclared();
}, LogicException::class, 'Call to undefined static method TestClass::undeclared().');
Assert::exception(function () {
$obj = new TestChild;
$obj->callParent();
}, LogicException::class, 'Call to undefined method parent::callParent().');
Assert::exception(function () {
$obj = new TestClass;
$obj->publicMethodX();
}, LogicException::class, 'Call to undefined method TestClass::publicMethodX(), did you mean publicMethod()?');
Assert::exception(function () { // suggest static method
$obj = new TestClass;
$obj->publicMethodStaticX();
}, LogicException::class, 'Call to undefined method TestClass::publicMethodStaticX(), did you mean publicMethodStatic()?');
Assert::exception(function () { // suggest only public method
$obj = new TestClass;
$obj->protectedMethodX();
}, LogicException::class, 'Call to undefined method TestClass::protectedMethodX().');
// writing
Assert::exception(function () {
$obj = new TestClass;
$obj->undeclared = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$undeclared.');
Assert::exception(function () {
$obj = new TestClass;
$obj->publicX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$publicX, did you mean $public?');
Assert::exception(function () { // suggest only non-static property
$obj = new TestClass;
$obj->publicStaticX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$publicStaticX.');
Assert::exception(function () { // suggest only public property
$obj = new TestClass;
$obj->protectedX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$protectedX.');
// property getter
$obj = new TestClass;
Assert::false(isset($obj->bar));
Assert::same(123, $obj->bar);
Assert::false(isset($obj->foo));
Assert::same(456, $obj->foo);
// reading
Assert::exception(function () {
$obj = new TestClass;
$val = $obj->undeclared;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$undeclared.');
Assert::exception(function () {
$obj = new TestClass;
$val = $obj->publicX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$publicX, did you mean $public?');
Assert::exception(function () { // suggest only non-static property
$obj = new TestClass;
$val = $obj->publicStaticX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$publicStaticX.');
Assert::exception(function () { // suggest only public property
$obj = new TestClass;
$val = $obj->protectedX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$protectedX.');
// unset/isset
Assert::exception(function () {
$obj = new TestClass;
unset($obj->undeclared);
}, LogicException::class, 'Attempt to unset undeclared property TestClass::$undeclared.');
Assert::false(isset($obj->undeclared));

View File

@@ -13,13 +13,16 @@ switch ($config['system']) {
case 'mysql': case 'mysql':
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null)); Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null)); Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
Assert::exception(function () use ($translator) { Assert::exception(
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null); fn() => $translator->formatValue(new DateInterval('P2Y4DT6H8M'), null),
}, Dibi\NotSupportedException::class, 'Only time interval is supported.'); Dibi\NotSupportedException::class,
'Only time interval is supported.',
);
break; break;
default: default:
Assert::exception(function () use ($translator) { Assert::exception(
$translator->formatValue(new DateInterval('PT10H20M30S'), null); fn() => $translator->formatValue(new DateInterval('PT10H20M30S'), null),
}, Dibi\Exception::class); Dibi\Exception::class,
);
} }

View File

@@ -91,9 +91,11 @@ Assert::same(
); );
// invalid input // invalid input
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->translate('SELECT %s', (object) [123], ', %m', 123); fn() => $conn->translate('SELECT %s', (object) [123], ', %m', 123),
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type stdClass and modifier %s'); Dibi\Exception::class,
'SQL translate error: Invalid combination of type stdClass and modifier %s',
);
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql()); Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
Assert::same( Assert::same(
@@ -176,9 +178,10 @@ Assert::same(
); );
if ($config['system'] === 'odbc') { if ($config['system'] === 'odbc') {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1); fn() => $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1),
}, Dibi\Exception::class); Dibi\Exception::class,
);
} else { } else {
// with limit = 2, offset = 1 // with limit = 2, offset = 1
Assert::same( Assert::same(
@@ -226,9 +229,11 @@ Assert::same(
]), ]),
); );
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('SELECT %s', new DateTime('1212-09-26')); fn() => $conn->translate('SELECT %s', new DateTime('1212-09-26')),
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s'); Dibi\Exception::class,
'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s',
);
@@ -268,9 +273,11 @@ if ($config['system'] === 'postgre') {
} }
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->translate("SELECT '"); fn() => $conn->translate("SELECT '"),
}, Dibi\Exception::class, 'SQL translate error: Alone quote'); Dibi\Exception::class,
'SQL translate error: Alone quote',
);
Assert::same('SELECT **Alone quote**', $e->getSql()); Assert::same('SELECT **Alone quote**', $e->getSql());
Assert::match( Assert::match(
@@ -647,13 +654,17 @@ Assert::same(
$conn->translate('INSERT INTO [test.*]'), $conn->translate('INSERT INTO [test.*]'),
); );
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('INSERT INTO %i', 'ahoj'); fn() => $conn->translate('INSERT INTO %i', 'ahoj'),
}, Dibi\Exception::class, "Expected number, 'ahoj' given."); Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('INSERT INTO %f', 'ahoj'); fn() => $conn->translate('INSERT INTO %f', 'ahoj'),
}, Dibi\Exception::class, "Expected number, 'ahoj' given."); Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
Assert::same( Assert::same(

View File

@@ -20,7 +20,7 @@ date_default_timezone_set('Europe/Prague');
try { try {
$config = Tester\Environment::loadData(); $config = Tester\Environment::loadData();
} catch (Throwable $e) { } catch (Throwable $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true); $config = parse_ini_file(__DIR__ . '/../databases.ini', process_sections: true);
$config = reset($config); $config = reset($config);
} }

View File

@@ -15,41 +15,54 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
new Dibi\Connection([ fn() => new Dibi\Connection([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'unknown', 'username' => 'unknown',
'password' => 'unknown', 'password' => 'unknown',
]); ]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::null($e->getSql()); Assert::null($e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT'); fn() => $conn->query('SELECT'),
}, Dibi\DriverException::class, '%a% error in your SQL syntax;%a%', 1064); Dibi\DriverException::class,
'%a% error in your SQL syntax;%a%',
1064,
);
Assert::same('SELECT', $e->getSql()); Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
}, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key '%a?%PRIMARY'", 1062); Dibi\UniqueConstraintViolationException::class,
"%a?%Duplicate entry '1' for key '%a?%PRIMARY'",
1062,
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (title) VALUES (NULL)'); fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
}, Dibi\NotNullConstraintViolationException::class, "%a?%Column 'title' cannot be null", 1048); Dibi\NotNullConstraintViolationException::class,
"%a?%Column 'title' cannot be null",
1048,
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'); fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% a foreign key constraint fails %a%', 1452); Dibi\ForeignKeyConstraintViolationException::class,
'%a% a foreign key constraint fails %a%',
1452,
);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql()); Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -15,29 +15,40 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT INTO'); fn() => $conn->query('SELECT INTO'),
}, Dibi\DriverException::class, '%a?%syntax error %A%'); Dibi\DriverException::class,
'%a?%syntax error %A%',
);
Assert::same('SELECT INTO', $e->getSql()); Assert::same('SELECT INTO', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
}, Dibi\UniqueConstraintViolationException::class, '%a% violates unique constraint %A%', '23505'); Dibi\UniqueConstraintViolationException::class,
'%a% violates unique constraint %A%',
'23505',
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (title) VALUES (NULL)'); fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
}, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title"%a%violates not-null constraint%A?%', '23502'); Dibi\NotNullConstraintViolationException::class,
'%a?%null value in column "title"%a%violates not-null constraint%A?%',
'23502',
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'); fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% violates foreign key constraint %A%', '23503'); Dibi\ForeignKeyConstraintViolationException::class,
'%a% violates foreign key constraint %A%',
'23503',
);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql()); Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -15,23 +15,32 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT'); fn() => $conn->query('SELECT'),
}, Dibi\DriverException::class, '%a%', 1); Dibi\DriverException::class,
'%a%',
1,
);
Assert::same('SELECT', $e->getSql()); Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
}, Dibi\UniqueConstraintViolationException::class, null, 19); Dibi\UniqueConstraintViolationException::class,
null,
19,
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (title) VALUES (NULL)'); fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
}, Dibi\NotNullConstraintViolationException::class, null, 19); Dibi\NotNullConstraintViolationException::class,
null,
19,
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());