diff --git a/composer.json b/composer.json index 1483f43..17c8427 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,9 @@ "autoload": { "psr-4": { "Nextras\\Dbal\\": "src/" } }, + "autoload-dev": { + "classmap": ["tests/inc/"] + }, "scripts": { "phpstan": "phpstan analyze -c .phpstan.neon --memory-limit=512M", "tests": "tester -C --colors 1 --setup ./tests/inc/setup.php ./tests/cases" diff --git a/docs/config-nette.md b/docs/config-nette.md index 8b281f8..9332c92 100644 --- a/docs/config-nette.md +++ b/docs/config-nette.md @@ -16,6 +16,10 @@ nextras.dbal: username: db-username password: db-password connectionTz: Europe/Prague + sqlProcessorFactory: @Custom\SqlProcessorFactory + +services: + - Custom\SqlProcessorFactory ``` If you need multiple connections, install the extension once again with a different name and choose which connection @@ -37,7 +41,8 @@ nextras.dbal2: **Configuration keys** are those accepted by `Connection` instance, the actual driver respectively. See [Connection](default) chapter. -The extension takes two additional configurations: +The extension takes additional configurations: - `panelQueryExplain` (default `true` if Tracy is available): enables/disables panel for Trace. - `maxQueries` (default `100`): number of logged queries in the Tracy panel. +- `sqlProcessorFactory` a reference to `Nextras\Dbal\ISqlProcessorFactory` service. diff --git a/docs/config-symfony.md b/docs/config-symfony.md index 0f9ff88..9ba5387 100644 --- a/docs/config-symfony.md +++ b/docs/config-symfony.md @@ -41,6 +41,8 @@ nextras_dbal: **Configuration keys** are those accepted by `Connection` instance, the actual driver respectively. See [Connection](default) chapter. -The bundle takes an additional configuration: +The bundle takes additional configurations: - `maxQueries` (default `100`): number of logged queries into QueryDataCollector. + +The define custom `Nextras\Dbal\ISqlProcessorFactory` instance, define `nextras_dbal.default.sqlProcessorFactory` named service, where the `default` is the name of relevant connection. diff --git a/docs/param-modifiers.md b/docs/param-modifiers.md index b899b0e..d0edeed 100644 --- a/docs/param-modifiers.md +++ b/docs/param-modifiers.md @@ -148,6 +148,8 @@ class SqlProcessorFactory implements ISqlProcessorFactory } ``` +Use `sqlProcessorFactory` configuration key to pass a factory instance. See configuration chapters. + ### Modifier Resolver SqlProcessor allows setting custom modifier resolver for any values passed for both implicit and explicit `%any` modifier. This way you may introduce custom processing for your custom types. For safety reasons it is possible to override only the `%any` modifier. To do so, implement `ISqlProcessorModifierResolver` interface and return the modifier name for the passed value. Finally, register the custom modifier resolver into SqlProcessor. This API is especially powerful in combination with custom modifiers. @@ -163,7 +165,7 @@ class BrickSqlProcessorModifierResolver implements ISqlProcessorModifierResolver public function resolve($value): ?string { if ($value instanceof \Brick\DayOfWeek) { - return 'brickDoW'; + return 'brickDayOfWeek'; } return null; } @@ -175,7 +177,7 @@ class SqlProcessorFactory implements ISqlProcessorFactory { $processor = new SqlProcessor($driver); $processor->setCustomModifier( - 'brickDoW', + 'brickDayOfWeek', function (SqlProcessor $processor, $value) { assert($value instanceof \Brick\DayOfWeek); return $processor->processModifier('s', $value->getValue()); diff --git a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasDbalExtension.php b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasDbalExtension.php index 4f43ffb..adfa577 100644 --- a/src/Bridges/SymfonyBundle/DependencyInjection/NextrasDbalExtension.php +++ b/src/Bridges/SymfonyBundle/DependencyInjection/NextrasDbalExtension.php @@ -7,6 +7,7 @@ use Nextras\Dbal\Connection; use Nextras\Dbal\IConnection; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -53,6 +54,11 @@ private function loadConnection( int $maxQueries, ): void { + $config['sqlProcessorFactory'] = new Reference( + "nextras_dbal.$name.sqlProcessorFactory", + ContainerInterface::NULL_ON_INVALID_REFERENCE, + ); + $connectionDefinition = new Definition(Connection::class); $connectionDefinition->setArgument('$config', $config); $connectionDefinition->setPublic(true); diff --git a/src/Connection.php b/src/Connection.php index c3ceeff..deac2e7 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -13,7 +13,6 @@ use Nextras\Dbal\Utils\MultiLogger; use Nextras\Dbal\Utils\StrictObjectTrait; use function array_unshift; -use function assert; use function is_array; use function spl_object_hash; use function str_replace; @@ -349,7 +348,9 @@ private function createSqlProcessor(): SqlProcessor { if (isset($this->config['sqlProcessorFactory'])) { $factory = $this->config['sqlProcessorFactory']; - assert($factory instanceof ISqlProcessorFactory); + if (!$factory instanceof ISqlProcessorFactory) { + throw new InvalidArgumentException("Connection's 'sqlProcessorFactory' configuration key does not contain an instance of " . ISqlProcessorFactory::class . '.'); + } return $factory->create($this); } else { return new SqlProcessor($this->getPlatform()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 4764d75..d62e47f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,6 +2,7 @@ namespace NextrasTests\Dbal; + use Tester\Environment; @@ -10,18 +11,11 @@ exit(1); } -require_once __DIR__ . '/inc/TestCase.php'; -require_once __DIR__ . '/inc/TestLogger.php'; -require_once __DIR__ . '/inc/QueryBuilderTestCase.php'; -require_once __DIR__ . '/inc/IntegrationTestCase.php'; - - define('TEMP_DIR', __DIR__ . '/temp'); date_default_timezone_set('Europe/Prague'); Environment::setup(); - if (getenv(Environment::RUNNER)) { # Runner header('Content-type: text/plain'); diff --git a/tests/cases/integration/DbalBundleTest.phpt b/tests/cases/integration/DbalBundleTest.phpt index 2898f82..f82c65a 100644 --- a/tests/cases/integration/DbalBundleTest.phpt +++ b/tests/cases/integration/DbalBundleTest.phpt @@ -9,7 +9,9 @@ namespace NextrasTests\Dbal; use Nextras\Dbal\Bridges\SymfonyBundle\DependencyInjection\NextrasDbalExtension; use Nextras\Dbal\Connection; use Nextras\Dbal\IConnection; +use Nextras\Dbal\ISqlProcessorFactory; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Tester\Assert; @@ -36,6 +38,9 @@ class DbalBundleTest extends IntegrationTestCase ], ], ]); + $containerBuilder->addDefinitions([ + 'nextras_dbal.default.sqlProcessorFactory' => new Definition(SqlProcessorFactory::class), + ]); $containerBuilder->compile(); @@ -49,11 +54,13 @@ class DbalBundleTest extends IntegrationTestCase /** @var \Symfony\Component\DependencyInjection\Container $container */ $container = new $dicClass; - $connectionClass = $container->get('nextras_dbal.default.connection'); - Assert::type(Connection::class, $connectionClass); + $connection = $container->get('nextras_dbal.default.connection'); + Assert::type(Connection::class, $connection); + + $connection = $container->get(IConnection::class); + Assert::type(Connection::class, $connection); - $connectionClass = $container->get(IConnection::class); - Assert::type(Connection::class, $connectionClass); + Assert::type(ISqlProcessorFactory::class, $connection->getConfig()["sqlProcessorFactory"]); } } diff --git a/tests/cases/integration/DbalExtensionTest.configD.neon b/tests/cases/integration/DbalExtensionTest.configD.neon index ee9ccdb..26ac925 100644 --- a/tests/cases/integration/DbalExtensionTest.configD.neon +++ b/tests/cases/integration/DbalExtensionTest.configD.neon @@ -2,9 +2,13 @@ dbal: driver: mysqli username: bar password: foo + sqlProcessorFactory: @NextrasTests\Dbal\SqlProcessorFactory dbal2: driver: mysqli username: bar2 password: foo2 autowired: false + +services: + - NextrasTests\Dbal\SqlProcessorFactory diff --git a/tests/cases/integration/DbalExtensionTest.phpt b/tests/cases/integration/DbalExtensionTest.phpt index aaecb41..6db2614 100644 --- a/tests/cases/integration/DbalExtensionTest.phpt +++ b/tests/cases/integration/DbalExtensionTest.phpt @@ -91,6 +91,7 @@ class DbalExtensionTest extends IntegrationTestCase $connection = $dic->getByType(Connection::class); Assert::type(Connection::class, $connection); Assert::equal('bar', $connection->getConfig()['username']); + Assert::type(SqlProcessorFactory::class, $connection->getConfig()['sqlProcessorFactory']); $connection = $dic->getService('dbal2.connection'); Assert::type(Connection::class, $connection); diff --git a/tests/cases/integration/sqlPreprocessor.phpt b/tests/cases/integration/sqlPreprocessor.phpt index 9003953..f96ea19 100644 --- a/tests/cases/integration/sqlPreprocessor.phpt +++ b/tests/cases/integration/sqlPreprocessor.phpt @@ -8,12 +8,9 @@ namespace NextrasTests\Dbal; -use Nextras\Dbal\Exception\InvalidArgumentException; -use Nextras\Dbal\IConnection; use Nextras\Dbal\ISqlProcessorFactory; use Nextras\Dbal\Platforms\PostgreSqlPlatform; use Nextras\Dbal\Result\Row; -use Nextras\Dbal\SqlProcessor; use Tester\Assert; @@ -56,28 +53,11 @@ class SqlPreprocessorIntegrationTest extends IntegrationTestCase public function testCustomModifier() { - $sqlProcessorFactory = new class implements ISqlProcessorFactory { - public function create(IConnection $connection): SqlProcessor - { - $sqlProcessor = new SqlProcessor($connection->getPlatform()); - $sqlProcessor->setCustomModifier( - '%test', - function (SqlProcessor $sqlProcessor, $value, string $type) { - if (!is_array($value)) throw new InvalidArgumentException('%test modifer accepts only array.'); - return 'ARRAY[' . - implode(', ', array_map(function ($subValue) use ($sqlProcessor): string { - return $sqlProcessor->processModifier('any', $subValue); - }, $value)) . - ']'; - } - ); - return $sqlProcessor; - } - }; - $this->connection->connect(); + /** @var ISqlProcessorFactory $sqlProcessorFactory */ + $sqlProcessorFactory = $this->connection->getConfig()['sqlProcessorFactory']; $sqlProcessor = $sqlProcessorFactory->create($this->connection); - $result = $sqlProcessor->processModifier('%test', [1, '2', false, null]); + $result = $sqlProcessor->processModifier('%pgArray', [1, '2', false, null]); if ($this->connection->getPlatform()->getName() === PostgreSqlPlatform::NAME) { Assert::same("ARRAY[1, '2', FALSE, NULL]", $result); } else { diff --git a/tests/inc/IntegrationTestCase.php b/tests/inc/IntegrationTestCase.php index 339c9ef..3051a98 100644 --- a/tests/inc/IntegrationTestCase.php +++ b/tests/inc/IntegrationTestCase.php @@ -42,6 +42,7 @@ protected function createConnection($params = []) 'user' => null, 'password' => null, 'searchPath' => ['public'], + 'sqlProcessorFactory' => new SqlProcessorFactory(), ], Environment::loadData(), $params); return new Connection($options); } diff --git a/tests/inc/SqlProcessorFactory.php b/tests/inc/SqlProcessorFactory.php new file mode 100644 index 0000000..94f7a02 --- /dev/null +++ b/tests/inc/SqlProcessorFactory.php @@ -0,0 +1,30 @@ +getPlatform()); + $sqlProcessor->setCustomModifier( + '%pgArray', + function(SqlProcessor $sqlProcessor, $value, string $type) { + if (!is_array($value)) throw new InvalidArgumentException('%pgArray modifier accepts an array only.'); + return 'ARRAY[' . + implode(', ', array_map(function($subValue) use ($sqlProcessor): string { + return $sqlProcessor->processModifier('any', $subValue); + }, $value)) . + ']'; + }, + ); + return $sqlProcessor; + } +}