├── .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 |
--------------------------------------------------------------------------------