diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 752d7180..f4057634 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -18,11 +18,18 @@ jobs: uses: "shivammathur/setup-php@v2" with: coverage: "none" - extensions: "relay" + extensions: "redis, relay" php-version: "8.4" - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v2" + - name: "Compile Symfony container" + run: | + php -r " + require 'vendor/autoload.php'; + (new Snc\RedisBundle\Tests\Functional\App\Kernel('test', true))->boot(); + " + - name: "Run a static analysis with vimeo/psalm" run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)" diff --git a/composer.json b/composer.json index 4c4d41c0..2f3e74d3 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "phpunit/phpunit": "^9.6.34 || ^10.5.63", "predis/predis": "^3.1", "psalm/plugin-phpunit": "^0.19.3", + "psalm/plugin-symfony": "^5.0", "psr/http-message": "^2", "seec/phpunit-consecutive-params": "^1.1.4", "symfony/browser-kit": "^6.4 || ^7.3 || ^8.0", @@ -49,7 +50,7 @@ "symfony/twig-bundle": "^6.4 || ^7.3 || ^8.0", "symfony/web-profiler-bundle": "^6.4 || ^7.3 || ^8.0", "symfony/yaml": "^6.4 || ^7.3 || ^8.0", - "vimeo/psalm": "^5 || ^6 || ^7" + "vimeo/psalm": "^6 || ^7" }, "conflict": { "ext-redis": "<6.1.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d13aefab..fcb84265 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,6 +7,7 @@ + diff --git a/psalm.xml.dist b/psalm.xml.dist index c7f96af7..7803297a 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -1,6 +1,7 @@ + + + + + + + tests/Functional/App/var/cache/test/Snc_RedisBundle_Tests_Functional_App_KernelTestDebugContainer.xml + diff --git a/src/DependencyInjection/Compiler/LoggingPass.php b/src/DependencyInjection/Compiler/LoggingPass.php index 6b8cf7b7..65711ef1 100644 --- a/src/DependencyInjection/Compiler/LoggingPass.php +++ b/src/DependencyInjection/Compiler/LoggingPass.php @@ -45,7 +45,7 @@ public function process(ContainerBuilder $container): void $arguments = $option->getArgument(0); $connectionFactoryId = sprintf('snc_redis.%s_connectionfactory', $clientAlias); - $connectionFactoryDef = new Definition((string) $container->getParameter('snc_redis.connection_factory.class')); + $connectionFactoryDef = new Definition($container->getParameter('snc_redis.connection_factory.class')); if ($container->getParameter('kernel.debug')) { $connectionFactoryDef->addMethodCall('setStopwatch', [new Reference('debug.stopwatch', ContainerInterface::NULL_ON_INVALID_REFERENCE)]); } diff --git a/src/DependencyInjection/SncRedisExtension.php b/src/DependencyInjection/SncRedisExtension.php index be4d6cdb..b7ec4d66 100644 --- a/src/DependencyInjection/SncRedisExtension.php +++ b/src/DependencyInjection/SncRedisExtension.php @@ -180,12 +180,13 @@ private function loadPredisClient(array $client, ContainerBuilder $container): v } $optionId = sprintf('snc_redis.client.%s_options', $client['alias']); - $optionDef = new Definition((string) $container->getParameter('snc_redis.client_options.class')); + $optionDef = new Definition($container->getParameter('snc_redis.client_options.class')); $optionDef->addArgument($client['options']); $container->setDefinition($optionId, $optionDef); - $clientDef = new Definition($client['class'] ?? (string) $container->getParameter('snc_redis.client.class')); + $clientDef = new Definition($client['class'] ?? $container->getParameter('snc_redis.client.class')); $clientDef->addTag('snc_redis.client', ['alias' => $client['alias']]); - if ($connectionCount === 1 && !isset($client['options']['cluster']) && !isset($client['options']['replication'])) { + + if ($connectionCount === 1 && !isset($client['options']['replication'])) { $clientDef->addArgument(new Reference(sprintf('snc_redis.connection.%s_parameters.%s', $connectionAliases[0], $client['alias']))); } else { $connections = []; @@ -206,7 +207,7 @@ private function loadPredisClient(array $client, ContainerBuilder $container): v */ private function loadPredisConnectionParameters(string $clientAlias, array $options, ContainerBuilder $container, object $dsn): void { - $parametersClass = (string) $container->getParameter('snc_redis.connection_parameters.class'); + $parametersClass = $container->getParameter('snc_redis.connection_parameters.class'); $parameterId = sprintf('snc_redis.connection.%s_parameters.%s', $options['alias'], $clientAlias); $parameterDef = new Definition($parametersClass); @@ -261,7 +262,7 @@ private function loadMonolog(array $config, ContainerBuilder $container): void { $ref = new Reference(sprintf('snc_redis.%s', $config['monolog']['client'])); - $def = new Definition((string) $container->getParameter('snc_redis.monolog_handler.class'), [ + $def = new Definition($container->getParameter('snc_redis.monolog_handler.class'), [ $ref, $config['monolog']['key'], ]); @@ -277,6 +278,6 @@ private function loadMonolog(array $config, ContainerBuilder $container): void #[Override] public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface { - return new Configuration((bool) $container->getParameter('kernel.debug')); + return new Configuration($container->getParameter('kernel.debug')); } } diff --git a/src/Factory/PredisParametersFactory.php b/src/Factory/PredisParametersFactory.php index 637fcee0..7e10614a 100644 --- a/src/Factory/PredisParametersFactory.php +++ b/src/Factory/PredisParametersFactory.php @@ -9,21 +9,51 @@ use Snc\RedisBundle\DependencyInjection\Configuration\RedisDsn; use function array_filter; +use function array_map; use function array_merge; use function constant; +use function count; use function defined; use function is_a; +use function is_array; +use function is_string; use function sprintf; use function str_replace; /** @internal */ class PredisParametersFactory { + /** + * @param class-string $class + * @param array $options + * @param string|list|list> $dsn + * + * @return ParametersInterface|list + */ + public static function create(array $options, string $class, string|array $dsn): ParametersInterface|array + { + if (is_string($dsn)) { + $dsn = [$dsn]; + } + + // json:/csv: env processors can produce a single-element array wrapping the actual list + if (count($dsn) === 1 && is_array($dsn[0])) { + $dsn = $dsn[0]; + } + + $parameters = array_map( + static fn (string $d) => static::createFromSingleDsn($options, $class, $d), + $dsn, + ); + + return count($parameters) === 1 ? $parameters[0] : $parameters; + } + /** * @param class-string $class * @param array $options */ - public static function create(array $options, string $class, string $dsn): ParametersInterface + private static function createFromSingleDsn(array $options, string $class, string $dsn): ParametersInterface { if (!is_a($class, ParametersInterface::class, true)) { throw new InvalidArgumentException(sprintf('%s::%s requires $class argument to implement %s', self::class, __METHOD__, ParametersInterface::class)); diff --git a/tests/Factory/PredisParametersFactoryTest.php b/tests/Factory/PredisParametersFactoryTest.php index f9420f8c..c354cea0 100644 --- a/tests/Factory/PredisParametersFactoryTest.php +++ b/tests/Factory/PredisParametersFactoryTest.php @@ -185,4 +185,28 @@ public function testCreateMergesSslWithTlsVersion(): void $this->assertSame(STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, $ssl['crypto_type']); $this->assertFalse($ssl['verify_peer']); } + + /** + * @testWith ["redis://localhost"] + * [["redis://localhost"]] + * [[["redis://localhost"]]] + */ + public function testCreateReturnsSingleParameters(string|array $dsn): void + { + $result = PredisParametersFactory::create([], Parameters::class, $dsn); + $this->assertInstanceOf(Parameters::class, $result); + } + + /** + * @param array> $dsn + * + * @testWith [["redis://host1", "redis://host2"]] + * [[["redis://host1", "redis://host2"]]] + */ + public function testCreateReturnsMultipleParameters(array $dsn): void + { + $result = PredisParametersFactory::create([], Parameters::class, $dsn); + $this->assertIsArray($result); + $this->assertCount(2, $result); + } } diff --git a/tests/Functional/App/Controller/Controller.php b/tests/Functional/App/Controller/Controller.php index d0193daa..3136e9cb 100644 --- a/tests/Functional/App/Controller/Controller.php +++ b/tests/Functional/App/Controller/Controller.php @@ -13,18 +13,28 @@ namespace Snc\RedisBundle\Tests\Functional\App\Controller; +use Predis\ClientInterface; use Redis; +use RedisArray; +use Relay\Relay; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; class Controller extends AbstractController { - public function __invoke(Redis $redis): JsonResponse + /** @param iterable $clients */ + public function __construct(private iterable $clients) { - $redis->set('foo', 'bar'); + } + + public function __invoke(): JsonResponse + { + $result = null; + foreach ($this->clients as $client) { + $client->set('foo', 'bar'); + $result = $client->get('foo'); + } - return new JsonResponse([ - 'result' => $redis->get('foo'), - ]); + return new JsonResponse(['result' => $result]); } } diff --git a/tests/Functional/App/config.yaml b/tests/Functional/App/config.yaml index 64e08d7a..38a3bfc4 100644 --- a/tests/Functional/App/config.yaml +++ b/tests/Functional/App/config.yaml @@ -35,11 +35,9 @@ snc_redis: cluster: type: predis alias: cluster - dsn: - - redis://sncredis@127.0.0.1/3 - - redis://sncredis@127.0.0.1/4 - - redis://sncredis@127.0.0.1/5 + dsn: "%env(json:REDIS_DSNS)%" options: + cluster: predis prefix: foo connection_timeout: 10 connection_persistent: true @@ -59,20 +57,14 @@ snc_redis: with_acl: type: predis alias: with_acl - dsn: redis://localhost/1 + dsn: redis://snc_redis:snc_password@localhost:7099/1 logging: false - options: - parameters: - username: my_user - password: sncredis services: _defaults: autowire: true autoconfigure: true public: false - bind: - Predis\Client $predisReplication: '@snc_redis.predis_replication' Redis: '@snc_redis.default' @@ -87,6 +79,14 @@ services: resource: './Controller' tags: ['controller.service_arguments'] + Snc\RedisBundle\Tests\Functional\App\Controller\PredisReplication: + bind: + Predis\Client $predisReplication: '@snc_redis.predis_replication' + + Snc\RedisBundle\Tests\Functional\App\Controller\Controller: + arguments: + $clients: !tagged_iterator snc_redis.client + var_dumper.cli_dumper: class: Symfony\Component\VarDumper\Dumper\CliDumper arguments: ['/dev/null'] \ No newline at end of file diff --git a/tests/Functional/IntegrationTest.php b/tests/Functional/IntegrationTest.php index a806c8d3..06080b55 100644 --- a/tests/Functional/IntegrationTest.php +++ b/tests/Functional/IntegrationTest.php @@ -69,8 +69,8 @@ public function testIntegration(): void $this->assertInstanceOf(RedisDataCollector::class, $collector); $this->assertInstanceOf(ResetInterface::class, $container = $this->client->getKernel()->getContainer()); $this->assertInstanceOf(RedisLogger::class, $redisLogger = $container->get('test.snc_redis.logger')); - $this->assertSame(5, $redisLogger->getNbCommands()); - $this->assertCount(5, $collector->getCommands()); + $this->assertSame(13, $redisLogger->getNbCommands()); + $this->assertCount(13, $collector->getCommands()); $container->reset(); $this->assertSame(0, $redisLogger->getNbCommands()); }