├── .gitignore ├── Tests ├── Fixtures │ └── app │ │ ├── config │ │ ├── config_test.yml │ │ └── config_test_sf6.yml │ │ └── AppKernel.php ├── TestCase.php ├── Kernel.php ├── DependencyInjection │ ├── ConfigurationTest.php │ └── RedisExtensionTest.php └── Redis │ └── ClientTest.php ├── phpstan.neon ├── phpstan-baseline.neon ├── SymfonyBundlesRedisBundle.php ├── Redis ├── FactoryInterface.php ├── Factory.php ├── Client.php └── ClientInterface.php ├── phpcs.xml.dist ├── phpunit.xml.dist ├── .github └── workflows │ ├── static-analysis.yaml │ ├── php.yaml │ └── symfony.yaml ├── DependencyInjection ├── Configuration.php └── RedisExtension.php ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | 4 | ###> squizlabs/php_codesniffer ### 5 | /.phpcs-cache 6 | /phpcs.xml 7 | ###< squizlabs/php_codesniffer ### 8 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/config/config_test.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: ~ 3 | secret: test 4 | session: 5 | storage_id: session.storage.mock_file 6 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/config/config_test_sf6.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: ~ 3 | secret: test 4 | session: 5 | storage_factory_id: session.storage.factory.mock_file 6 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 8 6 | paths: 7 | - DependencyInjection/ 8 | - Redis/ 9 | - Tests/ 10 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:children\\(\\)\\.$#" 5 | count: 1 6 | path: DependencyInjection/Configuration.php 7 | - 8 | message: '#^Comparison operation "<" between 70103 and 53000 is always false.$#' 9 | count: 1 10 | path: Tests/Fixtures/app/AppKernel.php 11 | -------------------------------------------------------------------------------- /SymfonyBundlesRedisBundle.php: -------------------------------------------------------------------------------- 1 | container = Kernel::make()->getContainer(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Redis/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | boot(); 21 | } 22 | 23 | return self::$instance; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Redis/ 16 | DependencyInjection/ 17 | Tests/ 18 | 19 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(ConfigurationInterface::class, $configuration); 18 | 19 | $configs = $processor->processConfiguration($configuration, []); 20 | 21 | $this->assertSame(['clients' => ['default' => ['$parameters' => [], '$options' => []]]], $configs); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ./Tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ./ 28 | 29 | ./Tests 30 | ./vendor 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yaml: -------------------------------------------------------------------------------- 1 | name: Code style and static analysis 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ master, develop ] 7 | 8 | jobs: 9 | php-cs-fixer: 10 | name: PHP-CS-Fixer 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Setup PHP 17 | uses: shivammathur/setup-php@v2 18 | 19 | - name: Install dependencies 20 | run: composer install --no-progress --no-interaction --prefer-dist 21 | 22 | - name: Run script 23 | run: vendor/bin/phpcs 24 | 25 | phpstan: 26 | name: PHPStan 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | 35 | - name: Install dependencies 36 | run: composer install --no-progress --no-interaction --prefer-dist 37 | 38 | - name: Run script 39 | run: vendor/bin/phpstan analyse 40 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode() 18 | ->children() 19 | ->arrayNode('clients') 20 | ->addDefaultChildrenIfNoneSet('default') 21 | ->useAttributeAsKey('default') 22 | ->prototype('array') 23 | ->children() 24 | ->arrayNode('$parameters')->prototype('variable')->end()->end() 25 | ->arrayNode('$options')->prototype('variable')->end()->end() 26 | ->end() 27 | ->end() 28 | ->end() 29 | ->end(); 30 | 31 | return $builder; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021 Symfony Bundles (Dmitry Khaperets) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony-bundles/redis-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Symfony Redis Bundle", 5 | "keywords": ["redis", "nosql", "symfony", "bundle"], 6 | "homepage": "https://github.com/symfony-bundles/redis-bundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Dmitry Khaperets", 11 | "email": "khaperets@gmail.com" 12 | } 13 | ], 14 | "autoload": { 15 | "psr-4": { 16 | "SymfonyBundles\\RedisBundle\\": "" 17 | }, 18 | "exclude-from-classmap": [ 19 | "/Tests/" 20 | ] 21 | }, 22 | "require": { 23 | "php": "^7.2||^8.0", 24 | "predis/predis": "~1.1||~2.2", 25 | "symfony/framework-bundle": "^4.3||^5.0||^6.0||^7.0", 26 | "symfony/yaml": "^4.3||^5.0||^6.0||^7.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^8.5||^9.0", 30 | "symfony/phpunit-bridge": "^5.0", 31 | "squizlabs/php_codesniffer": "^3.5", 32 | "phpstan/phpstan": "^1.11", 33 | "phpstan/phpstan-phpunit": "^1.4" 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "3.x-dev" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/Fixtures/app/AppKernel.php: -------------------------------------------------------------------------------- 1 | remove($this->getCacheDir()); 16 | } 17 | 18 | /** 19 | * registerBundles. 20 | * 21 | * @return object[] 22 | */ 23 | public function registerBundles(): array 24 | { 25 | return [ 26 | new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 27 | new \SymfonyBundles\RedisBundle\SymfonyBundlesRedisBundle(), 28 | ]; 29 | } 30 | 31 | public function getRootDir(): string 32 | { 33 | return __DIR__; 34 | } 35 | 36 | public function getCacheDir(): string 37 | { 38 | return '/tmp/symfony-cache'; 39 | } 40 | 41 | public function getLogDir(): string 42 | { 43 | return '/tmp/symfony-cache'; 44 | } 45 | 46 | public function registerContainerConfiguration(LoaderInterface $loader): void 47 | { 48 | if (Kernel::VERSION_ID < 53000) { 49 | $loader->load($this->getRootDir() . '/config/config_test.yml'); 50 | 51 | return; 52 | } 53 | 54 | $loader->load($this->getRootDir() . '/config/config_test_sf6.yml'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/RedisExtensionTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Extension::class, $extension); 19 | 20 | $extension->load([], $container); 21 | 22 | $this->assertTrue($container->has('sb_redis.default')); 23 | $this->assertTrue($container->has(ClientInterface::class)); 24 | } 25 | 26 | public function testClientAlias(): void 27 | { 28 | $config = ['sb_redis' => ['clients' => ['test' => []]]]; 29 | 30 | $extension = new RedisExtension(); 31 | $container = new ContainerBuilder(); 32 | 33 | $this->assertFalse($container->has(ClientInterface::class)); 34 | 35 | $extension->load($config, $container); 36 | 37 | $this->assertTrue($container->has(ClientInterface::class)); 38 | } 39 | 40 | public function testAlias(): void 41 | { 42 | $extension = new RedisExtension(); 43 | 44 | $this->assertStringEndsWith('redis', $extension->getAlias()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Redis/Client.php: -------------------------------------------------------------------------------- 1 | call('lpop', [$key]); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function push(string $key, ...$values): int 31 | { 32 | return $this->call('rpush', array_merge([$key], $values)); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function count(string $key): int 39 | { 40 | return $this->call('llen', [$key]); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function remove(string $key): int 47 | { 48 | return $this->call('del', [$key]); 49 | } 50 | 51 | /** 52 | * Creates a Redis command with the specified arguments and sends a request to the server. 53 | * 54 | * @param string $command the command ID 55 | * @param mixed[] $arguments the arguments for the command 56 | * 57 | * @return mixed 58 | */ 59 | protected function call(string $command, array $arguments = []) 60 | { 61 | return $this->executeCommand($this->createCommand($command, $arguments)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Redis/ClientInterface.php: -------------------------------------------------------------------------------- 1 | setDefinition($factoryReference, new Definition(Redis\Factory::class)); 23 | 24 | foreach ($configs['clients'] as $name => $arguments) { 25 | $definition = new Definition(Redis\Client::class); 26 | $definition->setFactory([$factoryReference, 'create']); 27 | $definition->setArguments([$arguments['$parameters'], $arguments['$options']]); 28 | $definition->setPublic(true); 29 | 30 | $container->setDefinition(sprintf('%s.%s', $this->getAlias(), $name), $definition); 31 | 32 | if (false === $container->hasDefinition(Redis\ClientInterface::class)) { 33 | $container->setDefinition(Redis\ClientInterface::class, $definition); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getAlias(): string 42 | { 43 | return 'sb_redis'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/php.yaml: -------------------------------------------------------------------------------- 1 | name: PHP 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ master, develop ] 7 | 8 | jobs: 9 | run: 10 | name: Tests 11 | runs-on: ${{ matrix.os }} 12 | services: 13 | redis: 14 | image: redis 15 | ports: 16 | - 6379:6379 17 | options: --entrypoint redis-server 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | php: 24 | - '7.2' 25 | - '7.3' 26 | - '7.4' 27 | - '8.0' 28 | - '8.1' 29 | - '8.2' 30 | - '8.3' 31 | 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v2 35 | 36 | - name: Setup PHP 37 | uses: shivammathur/setup-php@v2 38 | with: 39 | php-version: ${{ matrix.php }} 40 | coverage: xdebug 41 | 42 | - name: Setup Problem Matchers for PHPUnit 43 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 44 | 45 | - name: Determine Composer cache directory 46 | id: composer-cache 47 | run: echo "::set-output name=directory::$(composer config cache-dir)" 48 | 49 | - name: Cache Composer dependencies 50 | uses: actions/cache@v2 51 | with: 52 | path: ${{ steps.composer-cache.outputs.directory }} 53 | key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} 54 | restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- 55 | 56 | - name: Install dependencies 57 | run: composer update --no-progress --no-interaction --prefer-dist 58 | 59 | - name: Run tests 60 | run: vendor/bin/phpunit --coverage-clover=coverage.xml 61 | 62 | - name: Upload coverage reports to Codecov 63 | uses: codecov/codecov-action@v4.2.0 64 | env: 65 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SymfonyBundlesRedisBundle 2 | ========================= 3 | 4 | [![Build][master Build Status Image]][master Build Status] 5 | [![Code Coverage][code-coverage-image]][code-coverage-link] 6 | [![Total Downloads][downloads-image]][package-link] 7 | [![Latest Stable Version][stable-image]][package-link] 8 | [![License][license-image]][license-link] 9 | 10 | Installation 11 | ------------ 12 | * Require the bundle with composer: 13 | 14 | ``` bash 15 | composer req symfony-bundles/redis-bundle 16 | ``` 17 | 18 | Configuring of the clients 19 | -------------------------- 20 | If you want to configure Redis clients - create a configuration file. For example: 21 | ``` yml 22 | # config/packages/sb_redis.yaml 23 | sb_redis: 24 | clients: 25 | default: 26 | $options: [] 27 | $parameters: ['tcp://127.0.0.1:6379?database=3'] 28 | 29 | ``` 30 | 31 | If you want to configure Redis clients Sentinel: 32 | ``` yml 33 | # config/packages/sb_redis.yaml 34 | sb_redis: 35 | clients: 36 | default: 37 | $options: 38 | replication: 'sentinel’ 39 | service: 'mymaster' 40 | parameters: 41 | database: '3' 42 | $parameters: ['%env(REDIS_URL)%', '%env(REDIS_URL)%'] 43 | 44 | ``` 45 | 46 | Read more about supported client options and connection parameters: 47 | 48 | * [Client Options][predis-options-link]. 49 | * [Connection Parameters][predis-parameters-link]. 50 | 51 | How to use 52 | ---------- 53 | Read more on the page [Quick tour][predis-quick-tour-link]. 54 | 55 | [package-link]: https://packagist.org/packages/symfony-bundles/redis-bundle 56 | [license-link]: https://github.com/symfony-bundles/redis-bundle/blob/master/LICENSE 57 | [license-image]: https://poser.pugx.org/symfony-bundles/redis-bundle/license 58 | [stable-image]: https://poser.pugx.org/symfony-bundles/redis-bundle/v/stable 59 | [downloads-image]: https://poser.pugx.org/symfony-bundles/redis-bundle/downloads 60 | [predis-quick-tour-link]: https://github.com/nrk/predis/wiki/Quick-tour 61 | [predis-options-link]: https://github.com/nrk/predis/wiki/Client-Options#list-of-supported-client-options 62 | [predis-parameters-link]: https://github.com/nrk/predis/wiki/Connection-Parameters#connection-parameters 63 | [master Build Status]: https://github.com/symfony-bundles/redis-bundle/actions?query=workflow%3ASymfony+branch%3Amaster 64 | [master Build Status Image]: https://github.com/symfony-bundles/redis-bundle/workflows/Symfony/badge.svg?branch=master 65 | [code-coverage-link]: https://codecov.io/gh/php-bundles/redis-bundle 66 | [code-coverage-image]: https://codecov.io/gh/php-bundles/redis-bundle/graph/badge.svg?token=N1xHW6iLGl 67 | -------------------------------------------------------------------------------- /.github/workflows/symfony.yaml: -------------------------------------------------------------------------------- 1 | name: Symfony 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ master, develop ] 7 | 8 | jobs: 9 | Tests: 10 | runs-on: ubuntu-latest 11 | services: 12 | redis: 13 | image: redis 14 | ports: 15 | - 6379:6379 16 | options: --entrypoint redis-server 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | symfony-versions: 21 | - '4.4.*' 22 | - '5.0.*' 23 | - '5.1.*' 24 | - '5.2.*' 25 | - '5.3.*' 26 | - '5.4.*' 27 | 28 | php: 29 | - '7.2' 30 | - '7.3' 31 | - '7.4' 32 | - '8.0' 33 | 34 | include: 35 | - php: '8.0' 36 | symfony-versions: '^6.0' 37 | - php: '8.1' 38 | symfony-versions: '^6.0' 39 | - php: '8.2' 40 | symfony-versions: '^7.0' 41 | 42 | name: PHP ${{ matrix.php }} Symfony ${{ matrix.symfony-versions }} 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v2 46 | 47 | - uses: actions/cache@v2 48 | with: 49 | path: ~/.composer/cache/files 50 | key: ${{ matrix.php }}-${{ matrix.symfony-versions }} 51 | 52 | - name: Setup PHP 53 | uses: shivammathur/setup-php@v2 54 | with: 55 | php-version: ${{ matrix.php }} 56 | coverage: xdebug 57 | extensions: redis 58 | 59 | - name: Add PHPUnit matcher 60 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 61 | 62 | - name: Set composer cache directory 63 | id: composer-cache 64 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 65 | 66 | - name: Cache composer 67 | uses: actions/cache@v2.1.2 68 | with: 69 | path: ${{ steps.composer-cache.outputs.dir }} 70 | key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.symfony-versions }}-composer-${{ hashFiles('composer.json') }} 71 | restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.symfony-versions }}-composer 72 | 73 | - name: Install Symfony version 74 | run: | 75 | composer require symfony/framework-bundle:${{ matrix.symfony-versions }} --no-update --no-scripts 76 | composer require symfony/yaml:${{ matrix.symfony-versions }} --no-update --no-scripts 77 | 78 | - name: Install dependencies 79 | run: composer install 80 | 81 | - name: Run PHPUnit tests 82 | run: vendor/bin/phpunit --coverage-clover=coverage.xml 83 | 84 | - name: Upload coverage reports to Codecov 85 | uses: codecov/codecov-action@v4.2.0 86 | env: 87 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 88 | with: 89 | file: './coverage.xml' 90 | fail_ci_if_error: true 91 | -------------------------------------------------------------------------------- /Tests/Redis/ClientTest.php: -------------------------------------------------------------------------------- 1 | getClient(['sb_redis' => []]); 16 | 17 | $this->assertInstanceOf(ClientInterface::class, $client); 18 | } 19 | 20 | public function testPush(): void 21 | { 22 | $client = $this->getClient(); 23 | 24 | $client->remove('mykey'); 25 | 26 | $this->assertSame(0, $client->count('mykey')); 27 | $this->assertSame(1, $client->push('mykey', 'foo')); 28 | $this->assertSame(3, $client->push('mykey', 'bar', 'baz')); 29 | $this->assertSame(3, $client->count('mykey')); 30 | } 31 | 32 | public function testPop(): void 33 | { 34 | $client = $this->getClient(); 35 | 36 | $client->remove('mykey'); 37 | 38 | $this->assertSame(0, $client->count('mykey')); 39 | $this->assertSame(3, $client->push('mykey', 'foo', 'bar', 'baz')); 40 | $this->assertSame('foo', $client->pop('mykey')); 41 | $this->assertSame('bar', $client->pop('mykey')); 42 | $this->assertSame('baz', $client->pop('mykey')); 43 | $this->assertSame(0, $client->count('mykey')); 44 | } 45 | 46 | public function testSingleConnection(): void 47 | { 48 | $client = $this->getClient([ 49 | 'sb_redis' => [ 50 | 'clients' => [ 51 | 'test' => [ 52 | '$options' => [], 53 | '$parameters' => ['tcp://127.0.0.1:6379'], 54 | ], 55 | ], 56 | ], 57 | ]); 58 | 59 | $this->assertSame('OK', $client->flushall()->getPayload()); 60 | } 61 | 62 | /** 63 | * getClient. 64 | * 65 | * @param mixed[] $config 66 | * 67 | * @return ClientInterface 68 | * 69 | * @throws Exception 70 | */ 71 | private function getClient(array $config = []): ClientInterface 72 | { 73 | $client = $this->loadExtension($config)->get(ClientInterface::class); 74 | 75 | if (!$client instanceof ClientInterface) { 76 | throw new Exception('Client not instanceof ClientInterface'); 77 | } 78 | 79 | return $client; 80 | } 81 | 82 | /** 83 | * loadExtension. 84 | * 85 | * @param mixed[] $config 86 | * 87 | * @return ContainerBuilder 88 | */ 89 | private function loadExtension(array $config = []): ContainerBuilder 90 | { 91 | $defaults = [ 92 | 'sb_redis' => [ 93 | 'clients' => [ 94 | 'test' => [], 95 | ], 96 | ], 97 | ]; 98 | 99 | $extension = new RedisExtension(); 100 | $container = new ContainerBuilder(); 101 | 102 | $extension->load(array_merge_recursive($defaults, $config), $container); 103 | 104 | return $container; 105 | } 106 | } 107 | --------------------------------------------------------------------------------