Skip to content

Commit

Permalink
Add Relay\Relay support
Browse files Browse the repository at this point in the history
  • Loading branch information
ostrolucky committed Dec 31, 2022
1 parent 4751362 commit 1dd46f1
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 49 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ jobs:
extensions: "${{ matrix.php-extensions }}"
ini-values: "zend.assertions=1, max_execution_time=30"

- name: "Install Relay"
run: |
curl -L "https://cachewerk.s3.amazonaws.com/relay/dev/relay-dev-php${{ matrix.php-version }}-debian-x86-64.tar.gz" | tar xz
cd relay-dev-php${{ matrix.php-version }}-debian-x86-64
sudo cp relay.ini $(php-config --ini-dir)
sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so
sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so
- name: "Install symfony/flex"
run: "composer require --no-progress --no-scripts --no-plugins symfony/flex"

Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ jobs:
coverage: "none"
php-version: "7.4"

- name: "Install Relay"
run: |
curl -L "https://cachewerk.s3.amazonaws.com/relay/dev/relay-dev-php7.4-debian-x86-64.tar.gz" | tar xz
cd relay-dev-php7.4-debian-x86-64
sudo cp relay.ini $(php-config --ini-dir)
sudo cp relay-pkg.so $(php-config --extension-dir)/relay.so
sudo sed -i "s/00000000-0000-0000-0000-000000000000/$(cat /proc/sys/kernel/random/uuid)/" $(php-config --extension-dir)/relay.so
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"

Expand Down
14 changes: 4 additions & 10 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## About ##

This bundle integrates [Predis](https://github.com/nrk/predis) and [phpredis](https://github.com/nicolasff/phpredis) into your Symfony application.
This bundle integrates [Predis](https://github.com/nrk/predis), [PhpRedis](https://github.com/nicolasff/phpredis) and [Relay](https://relay.so/) into your Symfony application.

## Installation ##

Expand Down Expand Up @@ -52,7 +52,7 @@ You have to configure at least one client. In the above example your service
container will contain the service `snc_redis.default` which will return a
`Predis` client.

Available types are `predis` and `phpredis`.
Available types are `predis`, `phpredis` and `relay`.

A more complex setup which contains a clustered client could look like this:

Expand Down Expand Up @@ -104,13 +104,13 @@ snc_redis:
Please note that the master dsn connection needs to be tagged with the ```master``` alias.
If not, `predis` will complain.

A setup using `predis` or `phpredis` sentinel replication could look like this:
A setup using `predis`, `phpredis` or `relay` sentinel replication could look like this:

``` yaml
snc_redis:
clients:
default:
type: predis
type: "predis" # or "phpredis", or "relay"
alias: default
dsn:
- redis://localhost:26379
Expand Down Expand Up @@ -255,12 +255,6 @@ framework:
provider: snc_redis.cache
```

### Profiler storage ###

:warning: this feature is not supported anymore since Symfony 4.4 and will be automatically disabled if you are using Symfony 4.4.

>As the profiler must only be used on non-production servers, the file storage is more than enough and no other implementations will ever be supported.

### Complete configuration example ###

``` yaml
Expand Down
2 changes: 2 additions & 0 deletions src/DependencyInjection/Configuration/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Predis\Connection\Parameters;
use Redis;
use RedisCluster;
use Relay\Relay;
use Snc\RedisBundle\Client\Predis\Connection\ConnectionFactory;
use Snc\RedisBundle\Client\Predis\Connection\ConnectionWrapper;
use Snc\RedisBundle\DataCollector\RedisDataCollector;
Expand Down Expand Up @@ -54,6 +55,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('connection_factory')->defaultValue(ConnectionFactory::class)->end()
->scalarNode('connection_wrapper')->defaultValue(ConnectionWrapper::class)->end()
->scalarNode('phpredis_client')->defaultValue(Redis::class)->end()
->scalarNode('relay_client')->defaultValue(Relay::class)->end()
->scalarNode('phpredis_clusterclient')->defaultValue(RedisCluster::class)->end()
->scalarNode('logger')->defaultValue(RedisLogger::class)->end()
->scalarNode('data_collector')->defaultValue(RedisDataCollector::class)->end()
Expand Down
5 changes: 4 additions & 1 deletion src/DependencyInjection/SncRedisExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ private function loadClient(array $client, ContainerBuilder $container): void
$this->loadPredisClient($client, $container);
break;
case 'phpredis':
case 'relay':
$this->loadPhpredisClient($client, $container);
break;
default:
Expand Down Expand Up @@ -217,7 +218,9 @@ private function loadPhpredisClient(array $options, ContainerBuilder $container)
throw new LogicException('You cannot have both cluster and sentinel enabled for same redis connection');
}

$phpredisClientClass = (string) $container->getParameter('snc_redis.phpredis_' . ($hasClusterOption ? 'cluster' : '') . 'client.class');
$phpredisClientClass = (string) $container->getParameter(
sprintf('snc_redis.%s_%sclient.class', $options['type'], ($hasClusterOption ? 'cluster' : '')),
);

$phpredisDef = new Definition($phpredisClientClass, [
$hasSentinelOption ? RedisSentinel::class : $phpredisClientClass,
Expand Down
66 changes: 36 additions & 30 deletions src/Factory/PhpredisClientFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
use RedisSentinel;
use ReflectionClass;
use ReflectionMethod;
use Relay\Relay;
use Relay\Sentinel;
use Snc\RedisBundle\DependencyInjection\Configuration\RedisDsn;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

use function array_key_exists;
use function array_keys;
use function array_map;
use function count;
use function defined;
use function get_class;
use function implode;
use function in_array;
use function is_a;
Expand Down Expand Up @@ -63,27 +65,28 @@ public function __construct(callable $interceptor, ?Configuration $proxyConfigur
* @param list<string|list<string>> $dsns Multiple DSN string
* @param mixed[] $options Options provided in bundle client config
*
* @return Redis|RedisCluster
* @return Redis|RedisCluster|Relay
*
* @throws InvalidConfigurationException
* @throws LogicException
*/
public function create(string $class, array $dsns, array $options, string $alias, bool $loggingEnabled)
{
$isRedis = is_a($class, Redis::class, true);
$isSentinel = is_a($class, RedisSentinel::class, true);
$isRelay = is_a($class, Relay::class, true);
$isSentinel = is_a($class, RedisSentinel::class, true) || is_a($class, Sentinel::class, true);
$isCluster = is_a($class, RedisCluster::class, true);

if (!$isRedis && !$isSentinel && !$isCluster) {
throw new LogicException(sprintf('The factory can only instantiate Redis|RedisCluster|RedisSentinel classes: "%s" asked', $class));
if (!$isRedis && !$isRelay && !$isSentinel && !$isCluster) {
throw new LogicException(sprintf('The factory can only instantiate Redis|Relay\Relay|RedisCluster|RedisSentinel|Relay\Sentinel classes: "%s" asked', $class));
}

// Normalize the DSNs, because using processed environment variables could lead to nested values.
$dsns = count($dsns) === 1 && is_array($dsns[0]) ? $dsns[0] : $dsns;

$parsedDsns = array_map(static fn (string $dsn) => new RedisDsn($dsn), $dsns);

if ($isRedis) {
if ($isRedis || $isRelay) {
if (count($parsedDsns) > 1) {
throw new LogicException('Cannot have more than 1 dsn with \Redis and \RedisArray is not supported yet.');
}
Expand All @@ -92,20 +95,26 @@ public function create(string $class, array $dsns, array $options, string $alias
}

if ($isSentinel) {
return $this->createClientFromSentinel($parsedDsns, $alias, $options, $loggingEnabled);
return $this->createClientFromSentinel($class, $parsedDsns, $alias, $options, $loggingEnabled);
}

return $this->createClusterClient($parsedDsns, $class, $alias, $options, $loggingEnabled);
}

/**
* @param class-string $class
* @param list<RedisDsn> $dsns
* @param array{service: ?string} $options
*
* @return Redis|Relay
*/
private function createClientFromSentinel(array $dsns, string $alias, array $options, bool $loggingEnabled): Redis
private function createClientFromSentinel(string $class, array $dsns, string $alias, array $options, bool $loggingEnabled)
{
$isRelay = is_a($class, Sentinel::class, true);
$sentinelClass = $isRelay ? Sentinel::class : RedisSentinel::class;

foreach ($dsns as $dsn) {
$address = (new RedisSentinel($dsn->getHost(), (int) $dsn->getPort()))->getMasterAddrByName($options['service']);
$address = (new $sentinelClass($dsn->getHost(), (int) $dsn->getPort()))->getMasterAddrByName($options['service']);

if (!$address) {
continue;
Expand All @@ -120,7 +129,7 @@ public function __construct(string $dsn, string $host, int $port)
$this->port = $port;
}
},
Redis::class,
$isRelay ? Relay::class : Redis::class,
$alias,
$options,
$loggingEnabled,
Expand Down Expand Up @@ -155,7 +164,7 @@ private function createClusterClient(array $dsns, string $class, string $alias,
);

if (isset($options['prefix'])) {
$client->setOption(Redis::OPT_PREFIX, $options['prefix']);
$client->setOption(2, $options['prefix']);
}

if (isset($options['serialization'])) {
Expand All @@ -169,8 +178,12 @@ private function createClusterClient(array $dsns, string $class, string $alias,
return $loggingEnabled ? $this->createLoggingProxy($client, $alias) : $client;
}

/** @param mixed[] $options */
private function createClient(RedisDsn $dsn, string $class, string $alias, array $options, bool $loggingEnabled): Redis
/**
* @param mixed[] $options
*
* @return Redis|Relay
*/
private function createClient(RedisDsn $dsn, string $class, string $alias, array $options, bool $loggingEnabled)
{
$client = new $class();

Expand Down Expand Up @@ -229,24 +242,18 @@ private function createClient(RedisDsn $dsn, string $class, string $alias, array
return $client;
}

/**
* @return Redis::SERIALIZER_*
*
* @throws InvalidConfigurationException
*/
/** @throws InvalidConfigurationException */
private function loadSerializationType(string $type): int
{
$types = [
'default' => Redis::SERIALIZER_NONE,
'json' => Redis::SERIALIZER_JSON,
'none' => Redis::SERIALIZER_NONE,
'php' => Redis::SERIALIZER_PHP,
'default' => 0, // Redis::SERIALIZER_NONE,
'none' => 0, // Redis::SERIALIZER_NONE,
'php' => 1, //Redis::SERIALIZER_PHP,
'igbinary' => 2, //Redis::SERIALIZER_IGBINARY,
'msgpack' => 3, //Redis::SERIALIZER_MSGPACK,
'json' => 4, // Redis::SERIALIZER_JSON,
];

if (defined('Redis::SERIALIZER_IGBINARY')) {
$types['igbinary'] = Redis::SERIALIZER_IGBINARY;
}

if (array_key_exists($type, $types)) {
return $types[$type];
}
Expand Down Expand Up @@ -275,14 +282,13 @@ private function loadSlaveFailoverType(string $type): int
*
* @return T
*
* @template T of Redis|RedisCluster
* @template T of Redis|Relay|RedisCluster
*/
private function createLoggingProxy(object $client, string $alias): object
{
$prefixInterceptors = [];
$classToCopyMethodsFrom = $client instanceof Redis ? Redis::class : RedisCluster::class;
$prefixInterceptors = [];

foreach ((new ReflectionClass($classToCopyMethodsFrom))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
foreach ((new ReflectionClass(get_class($client)))->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$name = $method->getName();

if ($name[0] === '_' || in_array($name, self::CLIENT_ONLY_COMMANDS, true)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
parameters:
env(REDIS_URL): redis://localhost

snc_redis:
clients:
default:
type: relay
alias: default
dsn: "%env(REDIS_URL)%"
12 changes: 8 additions & 4 deletions tests/DependencyInjection/SncRedisExtensionEnvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ public function testPredisDefaultParameterWithSSLContextConfigLoad(): void
);
}

public function testPhpredisDefaultParameterConfig(): void
/**
* @testWith ["env_phpredis_minimal", "Redis"]
* ["env_relay_minimal", "Relay\\Relay"]
*/
public function testPhpredisDefaultParameterConfig(string $config, string $class): void
{
$container = $this->getConfiguredContainer('env_phpredis_minimal');
$container = $this->getConfiguredContainer($config);

$clientDefinition = $container->findDefinition('snc_redis.default');

$this->assertSame(Redis::class, $clientDefinition->getClass());
$this->assertSame(Redis::class, $clientDefinition->getArgument(0));
$this->assertSame($class, $clientDefinition->getClass());
$this->assertSame($class, $clientDefinition->getArgument(0));
$this->assertStringContainsString('REDIS_URL', $clientDefinition->getArgument(1)[0]);
$this->assertSame('default', $clientDefinition->getArgument(3));

Expand Down
2 changes: 2 additions & 0 deletions tests/DependencyInjection/SncRedisExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use PHPUnit\Framework\TestCase;
use Redis;
use RedisException;
use Relay\Relay;
use Snc\RedisBundle\DependencyInjection\Configuration\Configuration;
use Snc\RedisBundle\DependencyInjection\SncRedisExtension;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
Expand All @@ -43,6 +44,7 @@ public static function parameterValues(): array
{
return [
['snc_redis.client.class', 'Predis\Client'],
['snc_redis.relay_client.class', Relay::class],
['snc_redis.client_options.class', 'Predis\Configuration\Options'],
['snc_redis.connection_parameters.class', 'Predis\Connection\Parameters'],
['snc_redis.connection_factory.class', 'Snc\RedisBundle\Client\Predis\Connection\ConnectionFactory'],
Expand Down
26 changes: 22 additions & 4 deletions tests/Factory/PhpredisClientFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Psr\Log\LoggerInterface;
use Redis;
use RedisCluster;
use RedisSentinel;
use Relay\Relay;
use Snc\RedisBundle\Factory\PhpredisClientFactory;
use Snc\RedisBundle\Logger\RedisCallInterceptor;
use Snc\RedisBundle\Logger\RedisLogger;
Expand Down Expand Up @@ -51,6 +51,19 @@ public function testCreateMinimalConfig(): void
$this->assertNull($client->getPersistentID());
}

/** @requires extension relay */
public function testCreateRelay(): void
{
$this->logger->method('debug')->withConsecutive(
[$this->stringContains('Executing command "CONNECT localhost 6379 5 <null>')],
);

$client = (new PhpredisClientFactory(new RedisCallInterceptor($this->redisLogger)))
->create(Relay::class, ['redis://localhost:6379'], ['connection_timeout' => 5], 'default', true);

$this->assertInstanceOf(Relay::class, $client);
}

public function testUnixDsnConfig(): void
{
$this->logger->expects($this->never())->method('debug');
Expand Down Expand Up @@ -87,7 +100,12 @@ public function testCreateMinimalClusterConfig(): void
$this->assertSame(0, $client->getOption(RedisCluster::OPT_SLAVE_FAILOVER));
}

public function testCreatSentinelConfig(): void
/**
* @requires extension relay
* @testWith ["RedisSentinel", "Redis"]
* ["Relay\\Sentinel", "Relay\\Relay"]
*/
public function testCreateSentinelConfig(string $sentinelClass, string $outputClass): void
{
$this->logger->method('debug')->withConsecutive(
[$this->stringContains('Executing command "CONNECT 127.0.0.1 6379 5 <null>')],
Expand All @@ -96,14 +114,14 @@ public function testCreatSentinelConfig(): void
$factory = new PhpredisClientFactory(new RedisCallInterceptor($this->redisLogger));

$client = $factory->create(
RedisSentinel::class,
$sentinelClass,
['redis://sncredis@localhost:26379'],
['connection_timeout' => 5, 'connection_persistent' => false, 'service' => 'mymaster'],
'phpredissentinel',
true,
);

$this->assertInstanceOf(Redis::class, $client);
$this->assertInstanceOf($outputClass, $client);
$this->assertNull($client->getOption(Redis::OPT_PREFIX));
$this->assertSame(0, $client->getOption(Redis::OPT_SERIALIZER));
$this->assertSame('sncredis', $client->getAuth());
Expand Down

0 comments on commit 1dd46f1

Please sign in to comment.