Skip to content

Commit

Permalink
Merge pull request #208 from nextras/custom-sql-factory
Browse files Browse the repository at this point in the history
Update documentation & possibility to setup sqlProcessorFactory
  • Loading branch information
hrach authored May 13, 2023
2 parents 0a2fc35 7ded033 commit 4b38067
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 40 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 6 additions & 1 deletion docs/config-nette.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
4 changes: 3 additions & 1 deletion docs/config-symfony.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 4 additions & 2 deletions docs/param-modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -163,7 165,7 @@ class BrickSqlProcessorModifierResolver implements ISqlProcessorModifierResolver
public function resolve($value): ?string
{
if ($value instanceof \Brick\DayOfWeek) {
return 'brickDoW';
return 'brickDayOfWeek';
}
return null;
}
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
8 changes: 1 addition & 7 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 2,7 @@

namespace NextrasTests\Dbal;


use Tester\Environment;


Expand All @@ -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');
Expand Down
15 changes: 11 additions & 4 deletions tests/cases/integration/DbalBundleTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -36,6 38,9 @@ class DbalBundleTest extends IntegrationTestCase
],
],
]);
$containerBuilder->addDefinitions([
'nextras_dbal.default.sqlProcessorFactory' => new Definition(SqlProcessorFactory::class),
]);

$containerBuilder->compile();

Expand All @@ -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"]);
}
}

Expand Down
4 changes: 4 additions & 0 deletions tests/cases/integration/DbalExtensionTest.configD.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions tests/cases/integration/DbalExtensionTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 3 additions & 23 deletions tests/cases/integration/sqlPreprocessor.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions tests/inc/IntegrationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 42,7 @@ protected function createConnection($params = [])
'user' => null,
'password' => null,
'searchPath' => ['public'],
'sqlProcessorFactory' => new SqlProcessorFactory(),
], Environment::loadData(), $params);
return new Connection($options);
}
Expand Down
30 changes: 30 additions & 0 deletions tests/inc/SqlProcessorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 1,30 @@
<?php declare(strict_types = 1);

namespace NextrasTests\Dbal;


use Nextras\Dbal\Exception\InvalidArgumentException;
use Nextras\Dbal\IConnection;
use Nextras\Dbal\ISqlProcessorFactory;
use Nextras\Dbal\SqlProcessor;


class SqlProcessorFactory implements ISqlProcessorFactory
{
public function create(IConnection $connection): SqlProcessor
{
$sqlProcessor = new SqlProcessor($connection->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;
}
}

0 comments on commit 4b38067

Please sign in to comment.