1
0
mirror of https://github.com/dg/dibi.git synced 2025-09-03 19:12:33 +02:00

Compare commits

..

11 Commits

Author SHA1 Message Date
David Grudl
09a974ed7a sorting [WIP] 2022-12-20 19:04:31 +01:00
Miloslav Hůla
124d52139c added ObjectTranslator (#420)
Object translator allows to translate any object passed to Translator into Dibi\Expression.
2022-12-20 19:00:13 +01:00
Miloslav Hůla
78646e1790 tests: remove dependency on dibi_test database name 2022-11-22 10:46:30 +01:00
David Grudl
cab1c5b5e6 used native PHP 8 functions 2022-11-18 04:46:47 +01:00
David Grudl
94df6db03d added PHP 8 typehints 2022-11-18 04:46:47 +01:00
David Grudl
0575f9ea17 added property typehints 2022-11-18 04:45:32 +01:00
David Grudl
76b8ed2108 removed support for PHP 7 2022-11-18 04:45:32 +01:00
David Grudl
4b1a2faa76 coding style 2022-11-18 04:45:32 +01:00
David Grudl
b931dbe13b composer: updated dependencies 2022-11-18 04:45:32 +01:00
David Grudl
a55e2a0cf8 requires PHP 8.0 2022-11-18 04:45:32 +01:00
David Grudl
6356f9f7a4 opened 5.0-dev 2022-11-18 04:45:32 +01:00
65 changed files with 724 additions and 370 deletions

4
.gitattributes vendored
View File

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi
```
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.3.
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.2.
Usage
@@ -341,7 +341,7 @@ $database->query('INSERT INTO users', [
There are three methods for dealing with transactions:
```php
$database->begin();
$database->beginTransaction();
$database->commit();

View File

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

View File

@@ -20,9 +20,15 @@ use Tracy;
*/
class Panel implements Tracy\IBarPanel
{
use Dibi\Strict;
/** maximum SQL length */
public static int $maxLength = 1000;
public bool|string $explain;
public int $filter;
private array $events = [];
@@ -165,7 +171,7 @@ class Panel implements Tracy\IBarPanel
private function getConnectionName(Dibi\Connection $connection): string
{
$driver = $connection->getConfig('driver');
return get_debug_type($driver)
return (is_object($driver) ? $driver::class : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
}

View File

@@ -20,19 +20,26 @@ use Traversable;
*/
class Connection implements IConnection
{
use Strict;
/** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
/** Current connection configuration */
private array $config;
/** @var string[] resultset formats */
private array $formats;
private ?Driver $driver = null;
private ?Translator $translator = null;
/** @var array<string, callable(object): Expression | null> */
/** @var array<string, callable(object): Expression> */
private array $translators = [];
private bool $sortTranslators = false;
private HashMap $substitutes;
private int $transactionDepth = 0;
@@ -521,68 +528,19 @@ class Connection implements IConnection
/********************* value objects translation ****************d*g**/
/**
* @param callable(object): Expression $translator
*/
public function setObjectTranslator(callable $translator): void
/** @param callable(object): Expression $translator */
public function addObjectTranslator(string $class, callable $translator): self
{
if (!$translator instanceof \Closure) {
$translator = \Closure::fromCallable($translator);
}
$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;
$this->translators[$class] = $translator;
uksort($this->translators, fn($a, $b) => class_exists($a, false) && is_subclass_of($a, $b) ? -1 : 1);
return $this;
}
public function translateObject(object $object): ?Expression
/** @return array<string, callable(object): Expression> */
public function getObjectTranslators(): array
{
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;
return $this->translators;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,9 +18,13 @@ use Dibi\Helpers;
*/
class FirebirdResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/**
* @param resource $resultSet
@@ -31,6 +35,17 @@ 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.
*/
@@ -89,6 +104,7 @@ class FirebirdResult implements Dibi\ResultDriver
*/
public function getResultResource(): mixed
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

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

View File

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

View File

@@ -17,7 +17,13 @@ use Dibi;
*/
class MySqliResult implements Dibi\ResultDriver
{
use Dibi\Strict;
private \mysqli_result $resultSet;
private bool $autoFree = true;
/** Is buffered (seekable and countable)? */
private bool $buffered;
@@ -28,6 +34,17 @@ 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.
*/
@@ -117,6 +134,7 @@ class MySqliResult implements Dibi\ResultDriver
*/
public function getResultResource(): \mysqli_result
{
$this->autoFree = false;
return $this->resultSet;
}

View File

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

View File

@@ -25,9 +25,13 @@ use Dibi;
*/
class OdbcDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var resource */
private $connection;
private ?int $affectedRows;
private bool $microseconds = true;

View File

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

View File

@@ -17,8 +17,13 @@ use Dibi;
*/
class OdbcResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
private bool $autoFree = true;
private int $row = 0;
@@ -31,6 +36,17 @@ 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.
*/
@@ -111,6 +127,7 @@ class OdbcResult implements Dibi\ResultDriver
*/
public function getResultResource(): mixed
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

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

View File

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

View File

@@ -17,9 +17,13 @@ use Dibi;
*/
class OracleResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/**
* @param resource $resultSet
@@ -30,6 +34,17 @@ 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.
*/
@@ -95,6 +110,7 @@ class OracleResult implements Dibi\ResultDriver
*/
public function getResultResource(): mixed
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

@@ -27,9 +27,14 @@ use PDO;
*/
class PdoDriver implements Dibi\Driver
{
use Dibi\Strict;
private ?PDO $connection;
private ?int $affectedRows;
private string $driverName;
private string $serverVersion = '';

View File

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

View File

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

View File

@@ -17,7 +17,10 @@ use Dibi;
*/
class PostgreReflector implements Dibi\Reflector
{
use Dibi\Strict;
private Dibi\Driver $driver;
private string $version;

View File

@@ -19,9 +19,13 @@ use PgSql;
*/
class PostgreResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource|PgSql\Result */
private $resultSet;
private bool $autoFree = true;
/**
* @param resource|PgSql\Result $resultSet
@@ -32,6 +36,17 @@ 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.
*/
@@ -98,6 +113,7 @@ class PostgreResult implements Dibi\ResultDriver
*/
public function getResultResource(): mixed
{
$this->autoFree = false;
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet
: null;

View File

@@ -25,8 +25,12 @@ use SQLite3;
*/
class SqliteDriver implements Dibi\Driver
{
use Dibi\Strict;
private SQLite3 $connection;
private string $fmtDate;
private string $fmtDateTime;

View File

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

View File

@@ -18,8 +18,12 @@ use Dibi\Helpers;
*/
class SqliteResult implements Dibi\ResultDriver
{
use Dibi\Strict;
private \SQLite3Result $resultSet;
private bool $autoFree = true;
public function __construct(\SQLite3Result $resultSet)
{
@@ -27,6 +31,17 @@ 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.
* @throws Dibi\NotSupportedException
@@ -92,6 +107,7 @@ class SqliteResult implements Dibi\ResultDriver
*/
public function getResultResource(): \SQLite3Result
{
$this->autoFree = false;
return $this->resultSet;
}

View File

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

View File

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

View File

@@ -17,9 +17,13 @@ use Dibi;
*/
class SqlsrvResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
private bool $autoFree = true;
/**
* @param resource $resultSet
@@ -30,6 +34,17 @@ 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.
*/
@@ -91,6 +106,7 @@ class SqlsrvResult implements Dibi\ResultDriver
*/
public function getResultResource(): mixed
{
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

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

View File

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

View File

@@ -27,8 +27,6 @@ namespace Dibi;
* @method Fluent innerJoin(...$table)
* @method Fluent rightJoin(...$table)
* @method Fluent outerJoin(...$table)
* @method Fluent union(Fluent $fluent)
* @method Fluent unionAll(Fluent $fluent)
* @method Fluent as(...$field)
* @method Fluent on(...$cond)
* @method Fluent and(...$cond)
@@ -45,6 +43,8 @@ namespace Dibi;
*/
class Fluent implements IDataSource
{
use Strict;
public const REMOVE = false;
public static array $masks = [
@@ -92,10 +92,15 @@ class Fluent implements IDataSource
];
private Connection $connection;
private array $setups = [];
private ?string $command = null;
private array $clauses = [];
private array $flags = [];
private $cursor;
/** normalized clauses */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

112
src/Dibi/Strict.php Normal file
View File

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

View File

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

View File

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

View File

@@ -24,16 +24,14 @@ class Time extends DateTimeImmutable
test('Without object translator', function () use ($conn) {
Assert::exception(
fn() => $conn->translate('?', new Email),
Dibi\Exception::class,
'SQL translate error: Unexpected Email',
);
Assert::exception(function () use ($conn) {
$conn->translate('?', new Email);
}, Dibi\Exception::class, 'SQL translate error: Unexpected Email');
});
test('Basics', function () use ($conn) {
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
Assert::same(
reformat([
'sqlsrv' => "N'address@example.com'",
@@ -55,7 +53,7 @@ test('DateTime', function () use ($conn) {
// With object translator
$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')));
Assert::same(
reformat([
'sqlsrv' => "OwnTime(N'12:13:14')",
@@ -88,7 +86,7 @@ test('DateTime', function () use ($conn) {
);
// But DateTime translation can be overloaded
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
$conn->addObjectTranslator(DateTimeInterface::class, fn() => new Dibi\Expression('OwnDateTime'));
Assert::same(
'OwnDateTime',
$conn->translate('?', $dt),
@@ -97,9 +95,9 @@ test('DateTime', function () use ($conn) {
test('Complex structures', function () use ($conn) {
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
@@ -119,37 +117,3 @@ 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,22 +15,18 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
/*
Assert::exception(
fn() => $conn->rollback(),
Dibi\Exception::class,
);
/*Assert::exception(function () use ($conn) {
$conn->rollback();
}, Dibi\Exception::class);
Assert::exception(
fn() => $conn->commit(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) {
$conn->commit();
}, Dibi\Exception::class);
$conn->begin();
Assert::exception(
fn() => $conn->begin(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) {
$conn->begin();
}, Dibi\Exception::class);
*/
@@ -57,16 +53,14 @@ test('begin() & commit()', function () use ($conn) {
test('transaction() fail', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
}),
Throwable::class,
'my exception',
);
});
}, Throwable::class, 'my exception');
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
@@ -82,8 +76,8 @@ test('transaction() success', function () use ($conn) {
test('nested transaction() call fail', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
@@ -94,10 +88,8 @@ test('nested transaction() call fail', function () use ($conn) {
]);
throw new Exception('my exception');
});
}),
Throwable::class,
'my exception',
);
});
}, Throwable::class, 'my exception');
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
@@ -119,27 +111,21 @@ test('nested transaction() call success', function () use ($conn) {
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$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(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$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(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$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,11 +147,9 @@ if ($config['system'] === 'mysql') {
->limit(' 1; DROP TABLE users')
->offset(' 1; DROP TABLE users');
Assert::exception(
fn() => (string) $fluent,
Dibi\Exception::class,
"Expected number, ' 1; DROP TABLE users' given.",
);
Assert::exception(function () use ($fluent) {
(string) $fluent;
}, Dibi\Exception::class, "Expected number, ' 1; DROP TABLE users' given.");
}

View File

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

View File

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

View File

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

View File

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

151
tests/dibi/Strict.phpt Normal file
View File

@@ -0,0 +1,151 @@
<?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,16 +13,13 @@ switch ($config['system']) {
case 'mysql':
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::exception(
fn() => $translator->formatValue(new DateInterval('P2Y4DT6H8M'), null),
Dibi\NotSupportedException::class,
'Only time interval is supported.',
);
Assert::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
break;
default:
Assert::exception(
fn() => $translator->formatValue(new DateInterval('PT10H20M30S'), null),
Dibi\Exception::class,
);
Assert::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
}, Dibi\Exception::class);
}

View File

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

View File

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

View File

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

View File

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