Spaces:
No application file
No application file
declare(strict_types=1); | |
namespace Mautic\CoreBundle\Helper; | |
use Mautic\CoreBundle\Predis\Command\Unlink; | |
use Mautic\CoreBundle\Predis\Replication\MasterOnlyStrategy; | |
use Mautic\CoreBundle\Predis\Replication\StrategyConfig; | |
use Predis\Client; | |
use Predis\Cluster\ClusterStrategy; | |
use Predis\Connection\Aggregate\PredisCluster; | |
use Predis\Connection\Aggregate\RedisCluster; | |
use Predis\Connection\Aggregate\SentinelReplication; | |
use Predis\Profile\RedisProfile; | |
/** | |
* Helper functions for simpler operations with arrays. | |
*/ | |
class PRedisConnectionHelper | |
{ | |
/** | |
* Transform the redis url config key into an array if needed | |
* If possible also resolve the configured redis hostname to multiple IP addresses. | |
* | |
* @param mixed $configuredUrls a string or an array of redis endpoints to connect to | |
*/ | |
public static function getRedisEndpoints($configuredUrls): iterable | |
{ | |
if (is_iterable($configuredUrls)) { | |
// assume arrays are already in the correct format | |
return $configuredUrls; | |
} else { | |
$parsed = parse_url($configuredUrls); | |
if (!$parsed) { | |
return [$configuredUrls]; | |
} | |
// resolve hostnames ahead of time to support dns records with multiple ip addresses | |
// we need to provide each one to predis separately or it will just use a single one | |
$resolvedArray = gethostbynamel($parsed['host']); | |
if (!$resolvedArray) { | |
return [$configuredUrls]; | |
} else { | |
// this will return an array of associative arrays which is supported by Predis | |
return array_map(function ($i) use ($parsed) { | |
$parsed['host'] = $i; | |
return $parsed; | |
}, $resolvedArray); | |
} | |
} | |
} | |
/** | |
* Transform the redis mautic config to an options array consumable by PRedis. | |
* | |
* @param array $redisConfiguration mautic's redis configuration | |
*/ | |
public static function makeRedisOptions(array $redisConfiguration, string $prefix = ''): array | |
{ | |
$redisOptions = []; | |
if ($prefix) { | |
$redisOptions['prefix'] = $prefix; | |
} | |
if (!empty($redisConfiguration['replication'])) { | |
$redisOptions['replication'] = $redisConfiguration['replication']; | |
$redisOptions['service'] = $redisConfiguration['service'] ?? 'mymaster'; | |
} | |
if (!empty($redisConfiguration['password'])) { | |
$redisOptions['parameters'] = ['password' => $redisConfiguration['password']]; | |
} | |
foreach (['cluster', 'scheme', 'ssl'] as $key) { | |
if (isset($redisConfiguration[$key])) { | |
$redisOptions[$key] = $redisConfiguration[$key]; | |
} | |
} | |
return $redisOptions; | |
} | |
/** | |
* @param mixed[] $endpoints | |
* @param mixed[] $inputOptions | |
*/ | |
public static function createClient(array $endpoints, array $inputOptions): Client | |
{ | |
$replication = $inputOptions['replication'] ?? null; | |
if ('sentinel' === $replication) { | |
$inputOptions['aggregate'] = fn () => fn ($sentinels, $options): SentinelReplication => new SentinelReplication( | |
$options->service, | |
$sentinels, | |
$options->connections, | |
new MasterOnlyStrategy(StrategyConfig::fromArray($inputOptions)) | |
); | |
} | |
$client = new Client($endpoints, $inputOptions); | |
$profile = $client->getProfile(); | |
\assert($profile instanceof RedisProfile); | |
if (!$profile->getCommandClass(Unlink::ID)) { | |
$profile->defineCommand(Unlink::ID, Unlink::class); | |
} | |
$connection = $client->getConnection(); | |
if ($connection instanceof RedisCluster || $connection instanceof PredisCluster) { | |
$clusterStrategy = $connection->getClusterStrategy(); | |
if ($clusterStrategy instanceof ClusterStrategy && !in_array(Unlink::ID, $clusterStrategy->getSupportedCommands())) { | |
$clusterStrategy->setCommandHandler(Unlink::ID, [$clusterStrategy, 'getKeyFromAllArguments']); | |
} | |
} | |
return $client; | |
} | |
} | |