Upgrade framework

This commit is contained in:
2023-11-14 16:54:35 +01:00
parent 1648a5cd42
commit 4fcf6fffcc
10548 changed files with 693138 additions and 466698 deletions

View File

@@ -3,16 +3,39 @@
namespace Illuminate\Redis\Connections;
use Closure;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Redis\Events\CommandExecuted;
use Illuminate\Redis\Limiters\ConcurrencyLimiterBuilder;
use Illuminate\Redis\Limiters\DurationLimiterBuilder;
use Illuminate\Support\Traits\Macroable;
abstract class Connection
{
use Macroable {
__call as macroCall;
}
/**
* The Predis client.
* The Redis client.
*
* @var \Predis\Client
* @var \Redis
*/
protected $client;
/**
* The Redis connection name.
*
* @var string|null
*/
protected $name;
/**
* The event dispatcher instance.
*
* @var \Illuminate\Contracts\Events\Dispatcher
*/
protected $events;
/**
* Subscribe to a set of given channels for messages.
*
@@ -23,6 +46,28 @@ abstract class Connection
*/
abstract public function createSubscription($channels, Closure $callback, $method = 'subscribe');
/**
* Funnel a callback for a maximum number of simultaneous executions.
*
* @param string $name
* @return \Illuminate\Redis\Limiters\ConcurrencyLimiterBuilder
*/
public function funnel($name)
{
return new ConcurrencyLimiterBuilder($this, $name);
}
/**
* Throttle a callback for a maximum number of executions over a given duration.
*
* @param string $name
* @return \Illuminate\Redis\Limiters\DurationLimiterBuilder
*/
public function throttle($name)
{
return new DurationLimiterBuilder($this, $name);
}
/**
* Get the underlying Redis client.
*
@@ -61,12 +106,98 @@ abstract class Connection
* Run a command against the Redis database.
*
* @param string $method
* @param array $parameters
* @param array $parameters
* @return mixed
*/
public function command($method, array $parameters = [])
{
return $this->client->{$method}(...$parameters);
$start = microtime(true);
$result = $this->client->{$method}(...$parameters);
$time = round((microtime(true) - $start) * 1000, 2);
if (isset($this->events)) {
$this->event(new CommandExecuted($method, $parameters, $time, $this));
}
return $result;
}
/**
* Fire the given event if possible.
*
* @param mixed $event
* @return void
*/
protected function event($event)
{
$this->events?->dispatch($event);
}
/**
* Register a Redis command listener with the connection.
*
* @param \Closure $callback
* @return void
*/
public function listen(Closure $callback)
{
$this->events?->listen(CommandExecuted::class, $callback);
}
/**
* Get the connection name.
*
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* Set the connections name.
*
* @param string $name
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get the event dispatcher used by the connection.
*
* @return \Illuminate\Contracts\Events\Dispatcher
*/
public function getEventDispatcher()
{
return $this->events;
}
/**
* Set the event dispatcher instance on the connection.
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function setEventDispatcher(Dispatcher $events)
{
$this->events = $events;
}
/**
* Unset the event dispatcher instance on the connection.
*
* @return void
*/
public function unsetEventDispatcher()
{
$this->events = null;
}
/**
@@ -78,6 +209,10 @@ abstract class Connection
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
return $this->command($method, $parameters);
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace Illuminate\Redis\Connections;
use Redis;
use RuntimeException;
use UnexpectedValueException;
trait PacksPhpRedisValues
{
/**
* Indicates if Redis supports packing.
*
* @var bool|null
*/
protected $supportsPacking;
/**
* Indicates if Redis supports LZF compression.
*
* @var bool|null
*/
protected $supportsLzf;
/**
* Indicates if Redis supports Zstd compression.
*
* @var bool|null
*/
protected $supportsZstd;
/**
* Prepares the given values to be used with the `eval` command, including serialization and compression.
*
* @param array<int|string,string> $values
* @return array<int|string,string>
*/
public function pack(array $values): array
{
if (empty($values)) {
return $values;
}
if ($this->supportsPacking()) {
return array_map([$this->client, '_pack'], $values);
}
if ($this->compressed()) {
if ($this->supportsLzf() && $this->lzfCompressed()) {
if (! function_exists('lzf_compress')) {
throw new RuntimeException("'lzf' extension required to call 'lzf_compress'.");
}
$processor = function ($value) {
return \lzf_compress($this->client->_serialize($value));
};
} elseif ($this->supportsZstd() && $this->zstdCompressed()) {
if (! function_exists('zstd_compress')) {
throw new RuntimeException("'zstd' extension required to call 'zstd_compress'.");
}
$compressionLevel = $this->client->getOption(Redis::OPT_COMPRESSION_LEVEL);
$processor = function ($value) use ($compressionLevel) {
return \zstd_compress(
$this->client->_serialize($value),
$compressionLevel === 0 ? Redis::COMPRESSION_ZSTD_DEFAULT : $compressionLevel
);
};
} else {
throw new UnexpectedValueException(sprintf(
'Unsupported phpredis compression in use [%d].',
$this->client->getOption(Redis::OPT_COMPRESSION)
));
}
} else {
$processor = function ($value) {
return $this->client->_serialize($value);
};
}
return array_map($processor, $values);
}
/**
* Determine if compression is enabled.
*
* @return bool
*/
public function compressed(): bool
{
return defined('Redis::OPT_COMPRESSION') &&
$this->client->getOption(Redis::OPT_COMPRESSION) !== Redis::COMPRESSION_NONE;
}
/**
* Determine if LZF compression is enabled.
*
* @return bool
*/
public function lzfCompressed(): bool
{
return defined('Redis::COMPRESSION_LZF') &&
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZF;
}
/**
* Determine if ZSTD compression is enabled.
*
* @return bool
*/
public function zstdCompressed(): bool
{
return defined('Redis::COMPRESSION_ZSTD') &&
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_ZSTD;
}
/**
* Determine if LZ4 compression is enabled.
*
* @return bool
*/
public function lz4Compressed(): bool
{
return defined('Redis::COMPRESSION_LZ4') &&
$this->client->getOption(Redis::OPT_COMPRESSION) === Redis::COMPRESSION_LZ4;
}
/**
* Determine if the current PhpRedis extension version supports packing.
*
* @return bool
*/
protected function supportsPacking(): bool
{
if ($this->supportsPacking === null) {
$this->supportsPacking = $this->phpRedisVersionAtLeast('5.3.5');
}
return $this->supportsPacking;
}
/**
* Determine if the current PhpRedis extension version supports LZF compression.
*
* @return bool
*/
protected function supportsLzf(): bool
{
if ($this->supportsLzf === null) {
$this->supportsLzf = $this->phpRedisVersionAtLeast('4.3.0');
}
return $this->supportsLzf;
}
/**
* Determine if the current PhpRedis extension version supports Zstd compression.
*
* @return bool
*/
protected function supportsZstd(): bool
{
if ($this->supportsZstd === null) {
$this->supportsZstd = $this->phpRedisVersionAtLeast('5.1.0');
}
return $this->supportsZstd;
}
/**
* Determine if the PhpRedis extension version is at least the given version.
*
* @param string $version
* @return bool
*/
protected function phpRedisVersionAtLeast(string $version): bool
{
$phpredisVersion = phpversion('redis');
return $phpredisVersion !== false && version_compare($phpredisVersion, $version, '>=');
}
}

View File

@@ -4,5 +4,21 @@ namespace Illuminate\Redis\Connections;
class PhpRedisClusterConnection extends PhpRedisConnection
{
//
/**
* Flush the selected Redis database on all master nodes.
*
* @return mixed
*/
public function flushdb()
{
$arguments = func_get_args();
$async = strtoupper((string) ($arguments[0] ?? null)) === 'ASYNC';
foreach ($this->client->_masters() as $master) {
$async
? $this->command('rawCommand', [$master, 'flushdb', 'async'])
: $this->command('flushdb', [$master]);
}
}
}

View File

@@ -3,18 +3,45 @@
namespace Illuminate\Redis\Connections;
use Closure;
use Illuminate\Contracts\Redis\Connection as ConnectionContract;
use Illuminate\Support\Arr;
use Redis;
use RedisException;
class PhpRedisConnection extends Connection
/**
* @mixin \Redis
*/
class PhpRedisConnection extends Connection implements ConnectionContract
{
use PacksPhpRedisValues;
/**
* Create a new Predis connection.
* The connection creation callback.
*
* @var callable
*/
protected $connector;
/**
* The connection configuration array.
*
* @var array
*/
protected $config;
/**
* Create a new PhpRedis connection.
*
* @param \Redis $client
* @param callable|null $connector
* @param array $config
* @return void
*/
public function __construct($client)
public function __construct($client, callable $connector = null, array $config = [])
{
$this->client = $client;
$this->config = $config;
$this->connector = $connector;
}
/**
@@ -25,7 +52,7 @@ class PhpRedisConnection extends Connection
*/
public function get($key)
{
$result = $this->client->get($key);
$result = $this->command('get', [$key]);
return $result !== false ? $result : null;
}
@@ -40,17 +67,17 @@ class PhpRedisConnection extends Connection
{
return array_map(function ($value) {
return $value !== false ? $value : null;
}, $this->client->mget($keys));
}, $this->command('mget', [$keys]));
}
/**
* Set the string value in argument as value of the key.
* Set the string value in the argument as the value of the key.
*
* @param string $key
* @param mixed $value
* @param string|null $expireResolution
* @param int|null $expireTTL
* @param string|null $flag
* @param string $key
* @param mixed $value
* @param string|null $expireResolution
* @param int|null $expireTTL
* @param string|null $flag
* @return bool
*/
public function set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
@@ -58,16 +85,77 @@ class PhpRedisConnection extends Connection
return $this->command('set', [
$key,
$value,
$expireResolution ? [$expireResolution, $flag => $expireTTL] : null,
$expireResolution ? [$flag, $expireResolution => $expireTTL] : null,
]);
}
/**
* Set the given key if it doesn't exist.
*
* @param string $key
* @param string $value
* @return int
*/
public function setnx($key, $value)
{
return (int) $this->command('setnx', [$key, $value]);
}
/**
* Get the value of the given hash fields.
*
* @param string $key
* @param mixed $dictionary
* @return array
*/
public function hmget($key, ...$dictionary)
{
if (count($dictionary) === 1) {
$dictionary = $dictionary[0];
}
return array_values($this->command('hmget', [$key, $dictionary]));
}
/**
* Set the given hash fields to their respective values.
*
* @param string $key
* @param mixed $dictionary
* @return int
*/
public function hmset($key, ...$dictionary)
{
if (count($dictionary) === 1) {
$dictionary = $dictionary[0];
} else {
$input = collect($dictionary);
$dictionary = $input->nth(2)->combine($input->nth(2, 1))->toArray();
}
return $this->command('hmset', [$key, $dictionary]);
}
/**
* Set the given hash field if it doesn't exist.
*
* @param string $hash
* @param string $key
* @param string $value
* @return int
*/
public function hsetnx($hash, $key, $value)
{
return (int) $this->command('hsetnx', [$hash, $key, $value]);
}
/**
* Removes the first count occurrences of the value element from the list.
*
* @param string $key
* @param int $count
* @param $value $value
* @param mixed $value
* @return int|false
*/
public function lrem($key, $count, $value)
@@ -75,6 +163,32 @@ class PhpRedisConnection extends Connection
return $this->command('lrem', [$key, $value, $count]);
}
/**
* Removes and returns the first element of the list stored at key.
*
* @param mixed $arguments
* @return array|null
*/
public function blpop(...$arguments)
{
$result = $this->command('blpop', $arguments);
return empty($result) ? null : $result;
}
/**
* Removes and returns the last element of the list stored at key.
*
* @param mixed $arguments
* @return array|null
*/
public function brpop(...$arguments)
{
$result = $this->command('brpop', $arguments);
return empty($result) ? null : $result;
}
/**
* Removes and returns a random element from the set value at key.
*
@@ -82,9 +196,9 @@ class PhpRedisConnection extends Connection
* @param int|null $count
* @return mixed|false
*/
public function spop($key, $count = null)
public function spop($key, $count = 1)
{
return $this->command('spop', [$key]);
return $this->command('spop', func_get_args());
}
/**
@@ -96,25 +210,192 @@ class PhpRedisConnection extends Connection
*/
public function zadd($key, ...$dictionary)
{
if (count($dictionary) === 1) {
$_dictionary = [];
foreach ($dictionary[0] as $member => $score) {
$_dictionary[] = $score;
$_dictionary[] = $member;
if (is_array(end($dictionary))) {
foreach (array_pop($dictionary) as $member => $score) {
$dictionary[] = $score;
$dictionary[] = $member;
}
$dictionary = $_dictionary;
}
return $this->client->zadd($key, ...$dictionary);
$options = [];
foreach (array_slice($dictionary, 0, 3) as $i => $value) {
if (in_array($value, ['nx', 'xx', 'ch', 'incr', 'gt', 'lt', 'NX', 'XX', 'CH', 'INCR', 'GT', 'LT'], true)) {
$options[] = $value;
unset($dictionary[$i]);
}
}
return $this->command('zadd', array_merge([$key], [$options], array_values($dictionary)));
}
/**
* Return elements with score between $min and $max.
*
* @param string $key
* @param mixed $min
* @param mixed $max
* @param array $options
* @return array
*/
public function zrangebyscore($key, $min, $max, $options = [])
{
if (isset($options['limit']) && Arr::isAssoc($options['limit'])) {
$options['limit'] = [
$options['limit']['offset'],
$options['limit']['count'],
];
}
return $this->command('zRangeByScore', [$key, $min, $max, $options]);
}
/**
* Return elements with score between $min and $max.
*
* @param string $key
* @param mixed $min
* @param mixed $max
* @param array $options
* @return array
*/
public function zrevrangebyscore($key, $min, $max, $options = [])
{
if (isset($options['limit']) && Arr::isAssoc($options['limit'])) {
$options['limit'] = [
$options['limit']['offset'],
$options['limit']['count'],
];
}
return $this->command('zRevRangeByScore', [$key, $min, $max, $options]);
}
/**
* Find the intersection between sets and store in a new set.
*
* @param string $output
* @param array $keys
* @param array $options
* @return int
*/
public function zinterstore($output, $keys, $options = [])
{
return $this->command('zinterstore', [$output, $keys,
$options['weights'] ?? null,
$options['aggregate'] ?? 'sum',
]);
}
/**
* Find the union between sets and store in a new set.
*
* @param string $output
* @param array $keys
* @param array $options
* @return int
*/
public function zunionstore($output, $keys, $options = [])
{
return $this->command('zunionstore', [$output, $keys,
$options['weights'] ?? null,
$options['aggregate'] ?? 'sum',
]);
}
/**
* Scans all keys based on options.
*
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function scan($cursor, $options = [])
{
$result = $this->client->scan($cursor,
$options['match'] ?? '*',
$options['count'] ?? 10
);
if ($result === false) {
$result = [];
}
return $cursor === 0 && empty($result) ? false : [$cursor, $result];
}
/**
* Scans the given set for all values based on options.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function zscan($key, $cursor, $options = [])
{
$result = $this->client->zscan($key, $cursor,
$options['match'] ?? '*',
$options['count'] ?? 10
);
if ($result === false) {
$result = [];
}
return $cursor === 0 && empty($result) ? false : [$cursor, $result];
}
/**
* Scans the given hash for all values based on options.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function hscan($key, $cursor, $options = [])
{
$result = $this->client->hscan($key, $cursor,
$options['match'] ?? '*',
$options['count'] ?? 10
);
if ($result === false) {
$result = [];
}
return $cursor === 0 && empty($result) ? false : [$cursor, $result];
}
/**
* Scans the given set for all values based on options.
*
* @param string $key
* @param mixed $cursor
* @param array $options
* @return mixed
*/
public function sscan($key, $cursor, $options = [])
{
$result = $this->client->sscan($key, $cursor,
$options['match'] ?? '*',
$options['count'] ?? 10
);
if ($result === false) {
$result = [];
}
return $cursor === 0 && empty($result) ? false : [$cursor, $result];
}
/**
* Execute commands in a pipeline.
*
* @param callable $callback
* @return array|\Redis
* @param callable|null $callback
* @return \Redis|array
*/
public function pipeline(callable $callback = null)
{
@@ -128,8 +409,8 @@ class PhpRedisConnection extends Connection
/**
* Execute commands in a transaction.
*
* @param callable $callback
* @return array|\Redis
* @param callable|null $callback
* @return \Redis|array
*/
public function transaction(callable $callback = null)
{
@@ -156,18 +437,16 @@ class PhpRedisConnection extends Connection
}
/**
* Proxy a call to the eval function of PhpRedis.
* Evaluate a script and return its result.
*
* @param array $parameters
* @param string $script
* @param int $numberOfKeys
* @param dynamic $arguments
* @return mixed
*/
protected function proxyToEval(array $parameters)
public function eval($script, $numberOfKeys, ...$arguments)
{
return $this->command('eval', [
isset($parameters[0]) ? $parameters[0] : null,
array_slice($parameters, 2),
isset($parameters[1]) ? $parameters[1] : null,
]);
return $this->command('eval', [$script, $arguments, $numberOfKeys]);
}
/**
@@ -211,6 +490,22 @@ class PhpRedisConnection extends Connection
//
}
/**
* Flush the selected Redis database.
*
* @return mixed
*/
public function flushdb()
{
$arguments = func_get_args();
if (strtoupper((string) ($arguments[0] ?? null)) === 'ASYNC') {
return $this->command('flushdb', [true]);
}
return $this->command('flushdb');
}
/**
* Execute a raw command.
*
@@ -222,6 +517,32 @@ class PhpRedisConnection extends Connection
return $this->command('rawCommand', $parameters);
}
/**
* Run a command against the Redis database.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \RedisException
*/
public function command($method, array $parameters = [])
{
try {
return parent::command($method, $parameters);
} catch (RedisException $e) {
foreach (['went away', 'socket', 'read error on connection'] as $errorMessage) {
if (str_contains($e->getMessage(), $errorMessage)) {
$this->client = $this->connector ? call_user_func($this->connector) : $this->client;
break;
}
}
throw $e;
}
}
/**
* Disconnects from the Redis instance.
*
@@ -241,18 +562,6 @@ class PhpRedisConnection extends Connection
*/
public function __call($method, $parameters)
{
$method = strtolower($method);
if ($method == 'eval') {
return $this->proxyToEval($parameters);
}
if ($method == 'zrangebyscore' || $method == 'zrevrangebyscore') {
$parameters = array_map(function ($parameter) {
return is_array($parameter) ? array_change_key_case($parameter) : $parameter;
}, $parameters);
}
return parent::__call($method, $parameters);
return parent::__call(strtolower($method), $parameters);
}
}

View File

@@ -2,7 +2,19 @@
namespace Illuminate\Redis\Connections;
use Predis\Command\ServerFlushDatabase;
class PredisClusterConnection extends PredisConnection
{
//
/**
* Flush the selected Redis database on all cluster nodes.
*
* @return void
*/
public function flushdb()
{
$this->client->executeCommandOnNodes(
tap(new ServerFlushDatabase)->setArguments(func_get_args())
);
}
}

View File

@@ -3,9 +3,20 @@
namespace Illuminate\Redis\Connections;
use Closure;
use Illuminate\Contracts\Redis\Connection as ConnectionContract;
class PredisConnection extends Connection
/**
* @mixin \Predis\Client
*/
class PredisConnection extends Connection implements ConnectionContract
{
/**
* The Predis client.
*
* @var \Predis\Client
*/
protected $client;
/**
* Create a new Predis connection.
*
@@ -29,11 +40,11 @@ class PredisConnection extends Connection
{
$loop = $this->pubSubLoop();
call_user_func_array([$loop, $method], (array) $channels);
$loop->{$method}(...array_values((array) $channels));
foreach ($loop as $message) {
if ($message->kind === 'message' || $message->kind === 'pmessage') {
call_user_func($callback, $message->payload, $message->channel);
$callback($message->payload, $message->channel);
}
}

View File

@@ -2,30 +2,44 @@
namespace Illuminate\Redis\Connectors;
use Illuminate\Contracts\Redis\Connector;
use Illuminate\Redis\Connections\PhpRedisClusterConnection;
use Illuminate\Redis\Connections\PhpRedisConnection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Redis as RedisFacade;
use Illuminate\Support\Str;
use LogicException;
use Redis;
use RedisCluster;
use Illuminate\Support\Arr;
use Illuminate\Redis\Connections\PhpRedisConnection;
use Illuminate\Redis\Connections\PhpRedisClusterConnection;
class PhpRedisConnector
class PhpRedisConnector implements Connector
{
/**
* Create a new clustered Predis connection.
* Create a new clustered PhpRedis connection.
*
* @param array $config
* @param array $options
* @return \Illuminate\Redis\PhpRedisConnection
* @return \Illuminate\Redis\Connections\PhpRedisConnection
*/
public function connect(array $config, array $options)
{
return new PhpRedisConnection($this->createClient(array_merge(
$config, $options, Arr::pull($config, 'options', [])
)));
$formattedOptions = Arr::pull($config, 'options', []);
if (isset($config['prefix'])) {
$formattedOptions['prefix'] = $config['prefix'];
}
$connector = function () use ($config, $options, $formattedOptions) {
return $this->createClient(array_merge(
$config, $options, $formattedOptions
));
};
return new PhpRedisConnection($connector(), $connector, $config);
}
/**
* Create a new clustered Predis connection.
* Create a new clustered PhpRedis connection.
*
* @param array $config
* @param array $clusterOptions
@@ -42,14 +56,14 @@ class PhpRedisConnector
}
/**
* Build a single cluster seed string from array.
* Build a single cluster seed string from an array.
*
* @param array $server
* @return string
*/
protected function buildClusterConnectionString(array $server)
{
return $server['host'].':'.$server['port'].'?'.http_build_query(Arr::only($server, [
return $this->formatHost($server).':'.$server['port'].'?'.Arr::query(Arr::only($server, [
'database', 'password', 'prefix', 'read_timeout',
]));
}
@@ -59,18 +73,32 @@ class PhpRedisConnector
*
* @param array $config
* @return \Redis
*
* @throws \LogicException
*/
protected function createClient(array $config)
{
return tap(new Redis, function ($client) use ($config) {
if ($client instanceof RedisFacade) {
throw new LogicException(
extension_loaded('redis')
? 'Please remove or rename the Redis facade alias in your "app" configuration file in order to avoid collision with the PHP Redis extension.'
: 'Please make sure the PHP Redis extension is installed and enabled.'
);
}
$this->establishConnection($client, $config);
if (! empty($config['password'])) {
$client->auth($config['password']);
if (isset($config['username']) && $config['username'] !== '' && is_string($config['password'])) {
$client->auth([$config['username'], $config['password']]);
} else {
$client->auth($config['password']);
}
}
if (! empty($config['database'])) {
$client->select($config['database']);
if (isset($config['database'])) {
$client->select((int) $config['database']);
}
if (! empty($config['prefix'])) {
@@ -80,6 +108,26 @@ class PhpRedisConnector
if (! empty($config['read_timeout'])) {
$client->setOption(Redis::OPT_READ_TIMEOUT, $config['read_timeout']);
}
if (! empty($config['scan'])) {
$client->setOption(Redis::OPT_SCAN, $config['scan']);
}
if (! empty($config['name'])) {
$client->client('SETNAME', $config['name']);
}
if (array_key_exists('serializer', $config)) {
$client->setOption(Redis::OPT_SERIALIZER, $config['serializer']);
}
if (array_key_exists('compression', $config)) {
$client->setOption(Redis::OPT_COMPRESSION, $config['compression']);
}
if (array_key_exists('compression_level', $config)) {
$client->setOption(Redis::OPT_COMPRESSION_LEVEL, $config['compression_level']);
}
});
}
@@ -92,9 +140,25 @@ class PhpRedisConnector
*/
protected function establishConnection($client, array $config)
{
$client->{Arr::get($config, 'persistent', false) === true ? 'pconnect' : 'connect'}(
$config['host'], $config['port'], Arr::get($config, 'timeout', 0)
);
$persistent = $config['persistent'] ?? false;
$parameters = [
$this->formatHost($config),
$config['port'],
Arr::get($config, 'timeout', 0.0),
$persistent ? Arr::get($config, 'persistent_id', null) : null,
Arr::get($config, 'retry_interval', 0),
];
if (version_compare(phpversion('redis'), '3.1.3', '>=')) {
$parameters[] = Arr::get($config, 'read_timeout', 0.0);
}
if (version_compare(phpversion('redis'), '5.3.0', '>=') && ! is_null($context = Arr::get($config, 'context'))) {
$parameters[] = $context;
}
$client->{$persistent ? 'pconnect' : 'connect'}(...$parameters);
}
/**
@@ -106,12 +170,65 @@ class PhpRedisConnector
*/
protected function createRedisClusterInstance(array $servers, array $options)
{
return new RedisCluster(
$parameters = [
null,
array_values($servers),
Arr::get($options, 'timeout', 0),
Arr::get($options, 'read_timeout', 0),
isset($options['persistent']) && $options['persistent']
);
$options['timeout'] ?? 0,
$options['read_timeout'] ?? 0,
isset($options['persistent']) && $options['persistent'],
];
if (version_compare(phpversion('redis'), '4.3.0', '>=')) {
$parameters[] = $options['password'] ?? null;
}
if (version_compare(phpversion('redis'), '5.3.2', '>=') && ! is_null($context = Arr::get($options, 'context'))) {
$parameters[] = $context;
}
return tap(new RedisCluster(...$parameters), function ($client) use ($options) {
if (! empty($options['prefix'])) {
$client->setOption(RedisCluster::OPT_PREFIX, $options['prefix']);
}
if (! empty($options['scan'])) {
$client->setOption(RedisCluster::OPT_SCAN, $options['scan']);
}
if (! empty($options['failover'])) {
$client->setOption(RedisCluster::OPT_SLAVE_FAILOVER, $options['failover']);
}
if (! empty($options['name'])) {
$client->client('SETNAME', $options['name']);
}
if (array_key_exists('serializer', $options)) {
$client->setOption(RedisCluster::OPT_SERIALIZER, $options['serializer']);
}
if (array_key_exists('compression', $options)) {
$client->setOption(RedisCluster::OPT_COMPRESSION, $options['compression']);
}
if (array_key_exists('compression_level', $options)) {
$client->setOption(RedisCluster::OPT_COMPRESSION_LEVEL, $options['compression_level']);
}
});
}
/**
* Format the host using the scheme if available.
*
* @param array $options
* @return string
*/
protected function formatHost(array $options)
{
if (isset($options['scheme'])) {
return Str::start($options['host'], "{$options['scheme']}://");
}
return $options['host'];
}
}

View File

@@ -2,12 +2,13 @@
namespace Illuminate\Redis\Connectors;
use Predis\Client;
use Illuminate\Support\Arr;
use Illuminate\Redis\Connections\PredisConnection;
use Illuminate\Contracts\Redis\Connector;
use Illuminate\Redis\Connections\PredisClusterConnection;
use Illuminate\Redis\Connections\PredisConnection;
use Illuminate\Support\Arr;
use Predis\Client;
class PredisConnector
class PredisConnector implements Connector
{
/**
* Create a new clustered Predis connection.
@@ -22,6 +23,10 @@ class PredisConnector
['timeout' => 10.0], $options, Arr::pull($config, 'options', [])
);
if (isset($config['prefix'])) {
$formattedOptions['prefix'] = $config['prefix'];
}
return new PredisConnection(new Client($config, $formattedOptions));
}
@@ -37,6 +42,10 @@ class PredisConnector
{
$clusterSpecificOptions = Arr::pull($config, 'options', []);
if (isset($config['prefix'])) {
$clusterSpecificOptions['prefix'] = $config['prefix'];
}
return new PredisClusterConnection(new Client(array_values($config), array_merge(
$options, $clusterOptions, $clusterSpecificOptions
)));

View File

@@ -0,0 +1,59 @@
<?php
namespace Illuminate\Redis\Events;
class CommandExecuted
{
/**
* The Redis command that was executed.
*
* @var string
*/
public $command;
/**
* The array of command parameters.
*
* @var array
*/
public $parameters;
/**
* The number of milliseconds it took to execute the command.
*
* @var float
*/
public $time;
/**
* The Redis connection instance.
*
* @var \Illuminate\Redis\Connections\Connection
*/
public $connection;
/**
* The Redis connection name.
*
* @var string
*/
public $connectionName;
/**
* Create a new event instance.
*
* @param string $command
* @param array $parameters
* @param float|null $time
* @param \Illuminate\Redis\Connections\Connection $connection
* @return void
*/
public function __construct($command, $parameters, $time, $connection)
{
$this->time = $time;
$this->command = $command;
$this->parameters = $parameters;
$this->connection = $connection;
$this->connectionName = $connection->getName();
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,167 @@
<?php
namespace Illuminate\Redis\Limiters;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
use Illuminate\Support\Str;
use Throwable;
class ConcurrencyLimiter
{
/**
* The Redis factory implementation.
*
* @var \Illuminate\Redis\Connections\Connection
*/
protected $redis;
/**
* The name of the limiter.
*
* @var string
*/
protected $name;
/**
* The allowed number of concurrent tasks.
*
* @var int
*/
protected $maxLocks;
/**
* The number of seconds a slot should be maintained.
*
* @var int
*/
protected $releaseAfter;
/**
* Create a new concurrency limiter instance.
*
* @param \Illuminate\Redis\Connections\Connection $redis
* @param string $name
* @param int $maxLocks
* @param int $releaseAfter
* @return void
*/
public function __construct($redis, $name, $maxLocks, $releaseAfter)
{
$this->name = $name;
$this->redis = $redis;
$this->maxLocks = $maxLocks;
$this->releaseAfter = $releaseAfter;
}
/**
* Attempt to acquire the lock for the given number of seconds.
*
* @param int $timeout
* @param callable|null $callback
* @param int $sleep
* @return mixed
*
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
* @throws \Throwable
*/
public function block($timeout, $callback = null, $sleep = 250)
{
$starting = time();
$id = Str::random(20);
while (! $slot = $this->acquire($id)) {
if (time() - $timeout >= $starting) {
throw new LimiterTimeoutException;
}
usleep($sleep * 1000);
}
if (is_callable($callback)) {
try {
return tap($callback(), function () use ($slot, $id) {
$this->release($slot, $id);
});
} catch (Throwable $exception) {
$this->release($slot, $id);
throw $exception;
}
}
return true;
}
/**
* Attempt to acquire the lock.
*
* @param string $id A unique identifier for this lock
* @return mixed
*/
protected function acquire($id)
{
$slots = array_map(function ($i) {
return $this->name.$i;
}, range(1, $this->maxLocks));
return $this->redis->eval(...array_merge(
[$this->lockScript(), count($slots)],
array_merge($slots, [$this->name, $this->releaseAfter, $id])
));
}
/**
* Get the Lua script for acquiring a lock.
*
* KEYS - The keys that represent available slots
* ARGV[1] - The limiter name
* ARGV[2] - The number of seconds the slot should be reserved
* ARGV[3] - The unique identifier for this lock
*
* @return string
*/
protected function lockScript()
{
return <<<'LUA'
for index, value in pairs(redis.call('mget', unpack(KEYS))) do
if not value then
redis.call('set', KEYS[index], ARGV[3], "EX", ARGV[2])
return ARGV[1]..index
end
end
LUA;
}
/**
* Release the lock.
*
* @param string $key
* @param string $id
* @return void
*/
protected function release($key, $id)
{
$this->redis->eval($this->releaseScript(), 1, $key, $id);
}
/**
* Get the Lua script to atomically release a lock.
*
* KEYS[1] - The name of the lock
* ARGV[1] - The unique identifier for this lock
*
* @return string
*/
protected function releaseScript()
{
return <<<'LUA'
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
LUA;
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Illuminate\Redis\Limiters;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
use Illuminate\Support\InteractsWithTime;
class ConcurrencyLimiterBuilder
{
use InteractsWithTime;
/**
* The Redis connection.
*
* @var \Illuminate\Redis\Connections\Connection
*/
public $connection;
/**
* The name of the lock.
*
* @var string
*/
public $name;
/**
* The maximum number of entities that can hold the lock at the same time.
*
* @var int
*/
public $maxLocks;
/**
* The number of seconds to maintain the lock until it is automatically released.
*
* @var int
*/
public $releaseAfter = 60;
/**
* The amount of time to block until a lock is available.
*
* @var int
*/
public $timeout = 3;
/**
* The number of milliseconds to wait between attempts to acquire the lock.
*
* @var int
*/
public $sleep = 250;
/**
* Create a new builder instance.
*
* @param \Illuminate\Redis\Connections\Connection $connection
* @param string $name
* @return void
*/
public function __construct($connection, $name)
{
$this->name = $name;
$this->connection = $connection;
}
/**
* Set the maximum number of locks that can be obtained per time window.
*
* @param int $maxLocks
* @return $this
*/
public function limit($maxLocks)
{
$this->maxLocks = $maxLocks;
return $this;
}
/**
* Set the number of seconds until the lock will be released.
*
* @param int $releaseAfter
* @return $this
*/
public function releaseAfter($releaseAfter)
{
$this->releaseAfter = $this->secondsUntil($releaseAfter);
return $this;
}
/**
* Set the amount of time to block until a lock is available.
*
* @param int $timeout
* @return $this
*/
public function block($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* The number of milliseconds to wait between lock acquisition attempts.
*
* @param int $sleep
* @return $this
*/
public function sleep($sleep)
{
$this->sleep = $sleep;
return $this;
}
/**
* Execute the given callback if a lock is obtained, otherwise call the failure callback.
*
* @param callable $callback
* @param callable|null $failure
* @return mixed
*
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
*/
public function then(callable $callback, callable $failure = null)
{
try {
return (new ConcurrencyLimiter(
$this->connection, $this->name, $this->maxLocks, $this->releaseAfter
))->block($this->timeout, $callback, $this->sleep);
} catch (LimiterTimeoutException $e) {
if ($failure) {
return $failure($e);
}
throw $e;
}
}
}

View File

@@ -0,0 +1,203 @@
<?php
namespace Illuminate\Redis\Limiters;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
class DurationLimiter
{
/**
* The Redis factory implementation.
*
* @var \Illuminate\Redis\Connections\Connection
*/
private $redis;
/**
* The unique name of the lock.
*
* @var string
*/
private $name;
/**
* The allowed number of concurrent tasks.
*
* @var int
*/
private $maxLocks;
/**
* The number of seconds a slot should be maintained.
*
* @var int
*/
private $decay;
/**
* The timestamp of the end of the current duration.
*
* @var int
*/
public $decaysAt;
/**
* The number of remaining slots.
*
* @var int
*/
public $remaining;
/**
* Create a new duration limiter instance.
*
* @param \Illuminate\Redis\Connections\Connection $redis
* @param string $name
* @param int $maxLocks
* @param int $decay
* @return void
*/
public function __construct($redis, $name, $maxLocks, $decay)
{
$this->name = $name;
$this->decay = $decay;
$this->redis = $redis;
$this->maxLocks = $maxLocks;
}
/**
* Attempt to acquire the lock for the given number of seconds.
*
* @param int $timeout
* @param callable|null $callback
* @param int $sleep
* @return mixed
*
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
*/
public function block($timeout, $callback = null, $sleep = 750)
{
$starting = time();
while (! $this->acquire()) {
if (time() - $timeout >= $starting) {
throw new LimiterTimeoutException;
}
usleep($sleep * 1000);
}
if (is_callable($callback)) {
return $callback();
}
return true;
}
/**
* Attempt to acquire the lock.
*
* @return bool
*/
public function acquire()
{
$results = $this->redis->eval(
$this->luaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
);
$this->decaysAt = $results[1];
$this->remaining = max(0, $results[2]);
return (bool) $results[0];
}
/**
* Determine if the key has been "accessed" too many times.
*
* @return bool
*/
public function tooManyAttempts()
{
[$this->decaysAt, $this->remaining] = $this->redis->eval(
$this->tooManyAttemptsLuaScript(), 1, $this->name, microtime(true), time(), $this->decay, $this->maxLocks
);
return $this->remaining <= 0;
}
/**
* Clear the limiter.
*
* @return void
*/
public function clear()
{
$this->redis->del($this->name);
}
/**
* Get the Lua script for acquiring a lock.
*
* KEYS[1] - The limiter name
* ARGV[1] - Current time in microseconds
* ARGV[2] - Current time in seconds
* ARGV[3] - Duration of the bucket
* ARGV[4] - Allowed number of tasks
*
* @return string
*/
protected function luaScript()
{
return <<<'LUA'
local function reset()
redis.call('HMSET', KEYS[1], 'start', ARGV[2], 'end', ARGV[2] + ARGV[3], 'count', 1)
return redis.call('EXPIRE', KEYS[1], ARGV[3] * 2)
end
if redis.call('EXISTS', KEYS[1]) == 0 then
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
end
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
return {
tonumber(redis.call('HINCRBY', KEYS[1], 'count', 1)) <= tonumber(ARGV[4]),
redis.call('HGET', KEYS[1], 'end'),
ARGV[4] - redis.call('HGET', KEYS[1], 'count')
}
end
return {reset(), ARGV[2] + ARGV[3], ARGV[4] - 1}
LUA;
}
/**
* Get the Lua script to determine if the key has been "accessed" too many times.
*
* KEYS[1] - The limiter name
* ARGV[1] - Current time in microseconds
* ARGV[2] - Current time in seconds
* ARGV[3] - Duration of the bucket
* ARGV[4] - Allowed number of tasks
*
* @return string
*/
protected function tooManyAttemptsLuaScript()
{
return <<<'LUA'
if redis.call('EXISTS', KEYS[1]) == 0 then
return {0, ARGV[2] + ARGV[3]}
end
if ARGV[1] >= redis.call('HGET', KEYS[1], 'start') and ARGV[1] <= redis.call('HGET', KEYS[1], 'end') then
return {
redis.call('HGET', KEYS[1], 'end'),
ARGV[4] - redis.call('HGET', KEYS[1], 'count')
}
end
return {0, ARGV[2] + ARGV[3]}
LUA;
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Illuminate\Redis\Limiters;
use Illuminate\Contracts\Redis\LimiterTimeoutException;
use Illuminate\Support\InteractsWithTime;
class DurationLimiterBuilder
{
use InteractsWithTime;
/**
* The Redis connection.
*
* @var \Illuminate\Redis\Connections\Connection
*/
public $connection;
/**
* The name of the lock.
*
* @var string
*/
public $name;
/**
* The maximum number of locks that can be obtained per time window.
*
* @var int
*/
public $maxLocks;
/**
* The amount of time the lock window is maintained.
*
* @var int
*/
public $decay;
/**
* The amount of time to block until a lock is available.
*
* @var int
*/
public $timeout = 3;
/**
* The number of milliseconds to wait between attempts to acquire the lock.
*
* @var int
*/
public $sleep = 750;
/**
* Create a new builder instance.
*
* @param \Illuminate\Redis\Connections\Connection $connection
* @param string $name
* @return void
*/
public function __construct($connection, $name)
{
$this->name = $name;
$this->connection = $connection;
}
/**
* Set the maximum number of locks that can be obtained per time window.
*
* @param int $maxLocks
* @return $this
*/
public function allow($maxLocks)
{
$this->maxLocks = $maxLocks;
return $this;
}
/**
* Set the amount of time the lock window is maintained.
*
* @param \DateTimeInterface|\DateInterval|int $decay
* @return $this
*/
public function every($decay)
{
$this->decay = $this->secondsUntil($decay);
return $this;
}
/**
* Set the amount of time to block until a lock is available.
*
* @param int $timeout
* @return $this
*/
public function block($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* The number of milliseconds to wait between lock acquisition attempts.
*
* @param int $sleep
* @return $this
*/
public function sleep($sleep)
{
$this->sleep = $sleep;
return $this;
}
/**
* Execute the given callback if a lock is obtained, otherwise call the failure callback.
*
* @param callable $callback
* @param callable|null $failure
* @return mixed
*
* @throws \Illuminate\Contracts\Redis\LimiterTimeoutException
*/
public function then(callable $callback, callable $failure = null)
{
try {
return (new DurationLimiter(
$this->connection, $this->name, $this->maxLocks, $this->decay
))->block($this->timeout, $callback, $this->sleep);
} catch (LimiterTimeoutException $e) {
if ($failure) {
return $failure($e);
}
throw $e;
}
}
}

View File

@@ -2,12 +2,27 @@
namespace Illuminate\Redis;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use Closure;
use Illuminate\Contracts\Redis\Factory;
use Illuminate\Redis\Connections\Connection;
use Illuminate\Redis\Connectors\PhpRedisConnector;
use Illuminate\Redis\Connectors\PredisConnector;
use Illuminate\Support\Arr;
use Illuminate\Support\ConfigurationUrlParser;
use InvalidArgumentException;
/**
* @mixin \Illuminate\Redis\Connections\Connection
*/
class RedisManager implements Factory
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The name of the default driver.
*
@@ -15,6 +30,13 @@ class RedisManager implements Factory
*/
protected $driver;
/**
* The registered custom driver creators.
*
* @var array
*/
protected $customCreators = [];
/**
* The Redis server configurations.
*
@@ -29,14 +51,24 @@ class RedisManager implements Factory
*/
protected $connections;
/**
* Indicates whether event dispatcher is set on connections.
*
* @var bool
*/
protected $events = false;
/**
* Create a new Redis manager instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param string $driver
* @param array $config
* @return void
*/
public function __construct($driver, array $config)
public function __construct($app, $driver, array $config)
{
$this->app = $app;
$this->driver = $driver;
$this->config = $config;
}
@@ -55,7 +87,9 @@ class RedisManager implements Factory
return $this->connections[$name];
}
return $this->connections[$name] = $this->resolve($name);
return $this->connections[$name] = $this->configure(
$this->resolve($name), $name
);
}
/**
@@ -70,19 +104,20 @@ class RedisManager implements Factory
{
$name = $name ?: 'default';
$options = Arr::get($this->config, 'options', []);
$options = $this->config['options'] ?? [];
if (isset($this->config[$name])) {
return $this->connector()->connect($this->config[$name], $options);
return $this->connector()->connect(
$this->parseConnectionConfiguration($this->config[$name]),
array_merge(Arr::except($options, 'parameters'), ['parameters' => Arr::get($options, 'parameters.'.$name, Arr::get($options, 'parameters', []))])
);
}
if (isset($this->config['clusters'][$name])) {
return $this->resolveCluster($name);
}
throw new InvalidArgumentException(
"Redis connection [{$name}] not configured."
);
throw new InvalidArgumentException("Redis connection [{$name}] not configured.");
}
/**
@@ -93,26 +128,140 @@ class RedisManager implements Factory
*/
protected function resolveCluster($name)
{
$clusterOptions = Arr::get($this->config, 'clusters.options', []);
return $this->connector()->connectToCluster(
$this->config['clusters'][$name], $clusterOptions, Arr::get($this->config, 'options', [])
array_map(function ($config) {
return $this->parseConnectionConfiguration($config);
}, $this->config['clusters'][$name]),
$this->config['clusters']['options'] ?? [],
$this->config['options'] ?? []
);
}
/**
* Configure the given connection to prepare it for commands.
*
* @param \Illuminate\Redis\Connections\Connection $connection
* @param string $name
* @return \Illuminate\Redis\Connections\Connection
*/
protected function configure(Connection $connection, $name)
{
$connection->setName($name);
if ($this->events && $this->app->bound('events')) {
$connection->setEventDispatcher($this->app->make('events'));
}
return $connection;
}
/**
* Get the connector instance for the current driver.
*
* @return \Illuminate\Redis\Connectors\PhpRedisConnector|\Illuminate\Redis\Connectors\PredisConnector
* @return \Illuminate\Contracts\Redis\Connector|null
*/
protected function connector()
{
switch ($this->driver) {
case 'predis':
return new Connectors\PredisConnector;
case 'phpredis':
return new Connectors\PhpRedisConnector;
$customCreator = $this->customCreators[$this->driver] ?? null;
if ($customCreator) {
return $customCreator();
}
return match ($this->driver) {
'predis' => new PredisConnector,
'phpredis' => new PhpRedisConnector,
default => null,
};
}
/**
* Parse the Redis connection configuration.
*
* @param mixed $config
* @return array
*/
protected function parseConnectionConfiguration($config)
{
$parsed = (new ConfigurationUrlParser)->parseConfiguration($config);
$driver = strtolower($parsed['driver'] ?? '');
if (in_array($driver, ['tcp', 'tls'])) {
$parsed['scheme'] = $driver;
}
return array_filter($parsed, function ($key) {
return ! in_array($key, ['driver'], true);
}, ARRAY_FILTER_USE_KEY);
}
/**
* Return all of the created connections.
*
* @return array
*/
public function connections()
{
return $this->connections;
}
/**
* Enable the firing of Redis command events.
*
* @return void
*/
public function enableEvents()
{
$this->events = true;
}
/**
* Disable the firing of Redis command events.
*
* @return void
*/
public function disableEvents()
{
$this->events = false;
}
/**
* Set the default driver.
*
* @param string $driver
* @return void
*/
public function setDriver($driver)
{
$this->driver = $driver;
}
/**
* Disconnect the given connection and remove from local cache.
*
* @param string|null $name
* @return void
*/
public function purge($name = null)
{
$name = $name ?: 'default';
unset($this->connections[$name]);
}
/**
* Register a custom driver creator Closure.
*
* @param string $driver
* @param \Closure $callback
* @return $this
*/
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback->bindTo($this, $this);
return $this;
}
/**

View File

@@ -2,18 +2,12 @@
namespace Illuminate\Redis;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class RedisServiceProvider extends ServiceProvider
class RedisServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
@@ -22,9 +16,9 @@ class RedisServiceProvider extends ServiceProvider
public function register()
{
$this->app->singleton('redis', function ($app) {
$config = $app->make('config')->get('database.redis');
$config = $app->make('config')->get('database.redis', []);
return new RedisManager(Arr::pull($config, 'client', 'predis'), $config);
return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
});
$this->app->bind('redis.connection', function ($app) {

View File

@@ -14,19 +14,24 @@
}
],
"require": {
"php": ">=5.6.4",
"illuminate/contracts": "5.4.*",
"illuminate/support": "5.4.*",
"predis/predis": "~1.0"
"php": "^8.0.2",
"illuminate/collections": "^9.0",
"illuminate/contracts": "^9.0",
"illuminate/macroable": "^9.0",
"illuminate/support": "^9.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Redis\\": ""
}
},
"suggest": {
"ext-redis": "Required to use the phpredis connector (^4.0|^5.0).",
"predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2)."
},
"extra": {
"branch-alias": {
"dev-master": "5.4-dev"
"dev-master": "9.x-dev"
}
},
"config": {