diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index b3370342..94223747 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -41,6 +41,7 @@ jobs: with: php-version: 8.2 tools: phpstan + coverage: none - name: Get Composer cache directory id: composer-cache @@ -54,9 +55,7 @@ jobs: restore-keys: tests-php-${{ matrix.php }}-composer - name: Install Composer dependencies - env: - PHP_VERSION: ${{ matrix.php }} - run: composer install --ansi --no-progress --prefer-dist $(if [ "$PHP_VERSION" == "8.0" ]; then echo "--ignore-platform-reqs"; fi;) + run: composer install --ansi --no-progress --prefer-dist ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }} - name: Run PHPStan run: phpstan analyse diff --git a/src/Client.php b/src/Client.php index a6b05537..9caa2e49 100644 --- a/src/Client.php +++ b/src/Client.php @@ -392,11 +392,11 @@ class Client implements ClientInterface, IteratorAggregate * Creates a new pipeline context and returns it, or returns the results of * a pipeline executed inside the optionally provided callable object. * - * @param mixed ... Array of options, a callable for execution, or both. + * @param mixed ...$arguments Array of options, a callable for execution, or both. * * @return Pipeline|array */ - public function pipeline(/* arguments */) + public function pipeline(...$arguments) { return $this->sharedContextFactory('createPipeline', func_get_args()); } @@ -435,11 +435,11 @@ class Client implements ClientInterface, IteratorAggregate * Creates a new transaction context and returns it, or returns the results * of a transaction executed inside the optionally provided callable object. * - * @param mixed ... Array of options, a callable for execution, or both. + * @param mixed ...$arguments Array of options, a callable for execution, or both. * * @return MultiExecTransaction|array */ - public function transaction(/* arguments */) + public function transaction(...$arguments) { return $this->sharedContextFactory('createTransaction', func_get_args()); } @@ -467,11 +467,11 @@ class Client implements ClientInterface, IteratorAggregate * Creates a new publish/subscribe context and returns it, or starts its loop * inside the optionally provided callable object. * - * @param mixed ... Array of options, a callable for execution, or both. + * @param mixed ...$arguments Array of options, a callable for execution, or both. * * @return PubSubConsumer|null */ - public function pubSubLoop(/* arguments */) + public function pubSubLoop(...$arguments) { return $this->sharedContextFactory('createPubSub', func_get_args()); } @@ -497,6 +497,8 @@ class Client implements ClientInterface, IteratorAggregate $pubsub->stop(); } } + + return null; } /** diff --git a/src/Cluster/ClusterStrategy.php b/src/Cluster/ClusterStrategy.php index 61278c74..b12b8b0f 100644 --- a/src/Cluster/ClusterStrategy.php +++ b/src/Cluster/ClusterStrategy.php @@ -240,9 +240,11 @@ abstract class ClusterStrategy implements StrategyInterface { $arguments = $command->getArguments(); - if ($this->checkSameSlotForKeys($arguments)) { - return $arguments[0]; + if (!$this->checkSameSlotForKeys($arguments)) { + return null; } + + return $arguments[0]; } /** @@ -262,9 +264,11 @@ abstract class ClusterStrategy implements StrategyInterface $keys[] = $arguments[$i]; } - if ($this->checkSameSlotForKeys($keys)) { - return $arguments[0]; + if (!$this->checkSameSlotForKeys($keys)) { + return null; } + + return $arguments[0]; } /** @@ -291,9 +295,11 @@ abstract class ClusterStrategy implements StrategyInterface } } - if ($this->checkSameSlotForKeys($keys)) { - return $firstKey; + if (!$this->checkSameSlotForKeys($keys)) { + return null; } + + return $firstKey; } /** @@ -307,9 +313,11 @@ abstract class ClusterStrategy implements StrategyInterface { $arguments = $command->getArguments(); - if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { - return $arguments[0]; + if (!$this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) { + return null; } + + return $arguments[0]; } /** @@ -323,9 +331,11 @@ abstract class ClusterStrategy implements StrategyInterface { $arguments = $command->getArguments(); - if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { - return $arguments[1]; + if (!$this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) { + return null; } + + return $arguments[1]; } /** @@ -351,10 +361,8 @@ abstract class ClusterStrategy implements StrategyInterface } } - if ($this->checkSameSlotForKeys($keys)) { - return $arguments[0]; - } else { - return; + if (!$this->checkSameSlotForKeys($keys)) { + return null; } } @@ -373,9 +381,11 @@ abstract class ClusterStrategy implements StrategyInterface $arguments = $command->getArguments(); $keys = array_merge([$arguments[0]], array_slice($arguments, 2, $arguments[1])); - if ($this->checkSameSlotForKeys($keys)) { - return $arguments[0]; + if (!$this->checkSameSlotForKeys($keys)) { + return null; } + + return $arguments[0]; } /** @@ -387,15 +397,15 @@ abstract class ClusterStrategy implements StrategyInterface */ protected function getKeyFromScriptingCommands(CommandInterface $command) { - if ($command instanceof ScriptCommand) { - $keys = $command->getKeys(); - } else { - $keys = array_slice($args = $command->getArguments(), 2, $args[1]); + $keys = $command instanceof ScriptCommand + ? $command->getKeys() + : array_slice($args = $command->getArguments(), 2, $args[1]); + + if (!$keys || !$this->checkSameSlotForKeys($keys)) { + return null; } - if ($keys && $this->checkSameSlotForKeys($keys)) { - return $keys[0]; - } + return $keys[0]; } /** diff --git a/src/Cluster/SlotMap.php b/src/Cluster/SlotMap.php index 5ca1896b..0557c25e 100644 --- a/src/Cluster/SlotMap.php +++ b/src/Cluster/SlotMap.php @@ -30,7 +30,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable /** * Checks if the given slot is valid. * - * @param int $first Slot index. + * @param int $slot Slot index. * * @return bool */ @@ -145,16 +145,12 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable * * @param int $slot Slot index. * - * @return ?string + * @return string|null */ #[ReturnTypeWillChange] public function offsetGet($slot) { - if (isset($this->slots[$slot])) { - return $this->slots[$slot]; - } - - return null; + return $this->slots[$slot] ?? null; } /** @@ -162,6 +158,8 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable * * @param int $slot Slot index. * @param NodeConnectionInterface|string $connection ID or connection instance. + * + * @return void */ #[ReturnTypeWillChange] public function offsetSet($slot, $connection) @@ -177,6 +175,8 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable * Returns the node assigned to the specified slot. * * @param int $slot Slot index. + * + * @return void */ #[ReturnTypeWillChange] public function offsetUnset($slot) diff --git a/src/Command/Command.php b/src/Command/Command.php index b2c53e6c..9deb0c62 100644 --- a/src/Command/Command.php +++ b/src/Command/Command.php @@ -69,9 +69,7 @@ abstract class Command implements CommandInterface */ public function getSlot() { - if (isset($this->slot)) { - return $this->slot; - } + return $this->slot ?? null; } /** diff --git a/src/Command/Factory.php b/src/Command/Factory.php index c0b7745d..ed94dc57 100644 --- a/src/Command/Factory.php +++ b/src/Command/Factory.php @@ -49,15 +49,11 @@ abstract class Factory implements FactoryInterface * * @param string $commandID Command ID * - * @return ?string + * @return string|null */ public function getCommandClass(string $commandID): ?string { - if (isset($this->commands[$commandID = strtoupper($commandID)])) { - return $this->commands[$commandID]; - } - - return null; + return $this->commands[strtoupper($commandID)] ?? null; } /** @@ -138,7 +134,7 @@ abstract class Factory implements FactoryInterface /** * Returns the current command processor. * - * @return ?ProcessorInterface + * @return ProcessorInterface|null */ public function getProcessor(): ?ProcessorInterface { diff --git a/src/Command/FactoryInterface.php b/src/Command/FactoryInterface.php index ff56f9bc..e8195108 100644 --- a/src/Command/FactoryInterface.php +++ b/src/Command/FactoryInterface.php @@ -24,7 +24,7 @@ interface FactoryInterface /** * Checks if the command factory supports the specified list of commands. * - * @param array $commandIDs List of command IDs + * @param string ...$commandIDs List of command IDs * * @return bool */ diff --git a/src/Command/RawCommand.php b/src/Command/RawCommand.php index 3d312823..61b22311 100644 --- a/src/Command/RawCommand.php +++ b/src/Command/RawCommand.php @@ -43,11 +43,11 @@ final class RawCommand implements CommandInterface * Creates a new raw command using a variadic method. * * @param string $commandID Redis command ID - * @param string ... Arguments list for the command + * @param string ...$args Arguments list for the command * * @return CommandInterface */ - public static function create($commandID /* [ $arg, ... */) + public static function create($commandID, ...$args) { $arguments = func_get_args(); @@ -110,9 +110,7 @@ final class RawCommand implements CommandInterface */ public function getSlot() { - if (isset($this->slot)) { - return $this->slot; - } + return $this->slot ?? null; } /** diff --git a/src/Command/Redis/LMPOP.php b/src/Command/Redis/LMPOP.php index d37dd138..12ef06bf 100644 --- a/src/Command/Redis/LMPOP.php +++ b/src/Command/Redis/LMPOP.php @@ -52,10 +52,10 @@ class LMPOP extends RedisCommand public function parseResponse($data) { - if (null !== $data) { - return [$data[0] => $data[1]]; + if (null === $data) { + return null; } - return null; + return [$data[0] => $data[1]]; } } diff --git a/src/Configuration/OptionsInterface.php b/src/Configuration/OptionsInterface.php index 983920c0..597a0579 100644 --- a/src/Configuration/OptionsInterface.php +++ b/src/Configuration/OptionsInterface.php @@ -15,13 +15,13 @@ namespace Predis\Configuration; use Predis\Command\Processor\ProcessorInterface; /** - * @property callable $aggregate Custom aggregate connection initializer - * @property callable $cluster Aggregate connection initializer for clustering - * @property Connection\FactoryInterface $connections Connection factory for creating new connections - * @property bool $exceptions Toggles exceptions in client for -ERR responses - * @property ProcessorInterface $prefix Key prefixing strategy using the supplied string as prefix - * @property Command\FactoryInterface $commands Command factory for creating Redis commands - * @property callable $replication Aggregate connection initializer for replication + * @property callable $aggregate Custom aggregate connection initializer + * @property callable $cluster Aggregate connection initializer for clustering + * @property \Predis\Connection\FactoryInterface $connections Connection factory for creating new connections + * @property bool $exceptions Toggles exceptions in client for -ERR responses + * @property ProcessorInterface $prefix Key prefixing strategy using the supplied string as prefix + * @property \Predis\Command\FactoryInterface $commands Command factory for creating Redis commands + * @property callable $replication Aggregate connection initializer for replication */ interface OptionsInterface { diff --git a/src/Connection/Cluster/PredisCluster.php b/src/Connection/Cluster/PredisCluster.php index 99bc34bf..1fc87f77 100644 --- a/src/Connection/Cluster/PredisCluster.php +++ b/src/Connection/Cluster/PredisCluster.php @@ -46,7 +46,7 @@ class PredisCluster implements ClusterInterface, IteratorAggregate, Countable private $strategy; /** - * @var Predis\Cluster\Distributor\DistributorInterface + * @var \Predis\Cluster\Distributor\DistributorInterface */ private $distributor; @@ -149,9 +149,7 @@ class PredisCluster implements ClusterInterface, IteratorAggregate, Countable */ public function getConnectionById($id) { - if (isset($this->pool[$id])) { - return $this->pool[$id]; - } + return $this->pool[$id] ?? null; } /** @@ -163,15 +161,13 @@ class PredisCluster implements ClusterInterface, IteratorAggregate, Countable */ public function getConnectionByAlias($alias) { - if (isset($this->aliases[$alias])) { - return $this->aliases[$alias]; - } + return $this->aliases[$alias] ?? null; } /** * Retrieves a connection instance by slot. * - * @param string $key Key string. + * @param string $slot Slot name. * * @return NodeConnectionInterface|null */ diff --git a/src/Connection/Cluster/RedisCluster.php b/src/Connection/Cluster/RedisCluster.php index 85e979b6..adcad65f 100644 --- a/src/Connection/Cluster/RedisCluster.php +++ b/src/Connection/Cluster/RedisCluster.php @@ -238,9 +238,10 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable $retryAfter = $this->retryInterval; $command = RawCommand::create('CLUSTER', 'SLOTS'); - RETRY_COMMAND: + while ($retries <= $this->retryLimit) { try { $response = $connection->executeCommand($command); + break; } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); @@ -258,9 +259,8 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable usleep($retryAfter * 1000); $retryAfter = $retryAfter * 2; ++$retries; - - goto RETRY_COMMAND; } + } return $response; } @@ -395,9 +395,7 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable */ public function getConnectionById($connectionID) { - if (isset($this->pool[$connectionID])) { - return $this->pool[$connectionID]; - } + return $this->pool[$connectionID] ?? null; } /** @@ -407,9 +405,11 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable */ protected function getRandomConnection() { - if ($this->pool) { - return $this->pool[array_rand($this->pool)]; + if (!$this->pool) { + return null; } + + return $this->pool[array_rand($this->pool)]; } /** @@ -517,7 +517,7 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable $retries = 0; $retryAfter = $this->retryInterval; - RETRY_COMMAND: + while ($retries <= $this->retryLimit) { try { $response = $this->getConnectionByCommand($command)->$method($command); @@ -528,6 +528,8 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable throw new ServerException($message); } } + + break; } catch (Throwable $exception) { usleep($retryAfter * 1000); $retryAfter = $retryAfter * 2; @@ -543,14 +545,15 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable if ($retries === $this->retryLimit) { throw $exception; - } elseif ($this->useClusterSlots) { + } + + if ($this->useClusterSlots) { $this->askSlotMap(); } ++$retries; - - goto RETRY_COMMAND; } + } return $response; } diff --git a/src/Connection/CompositeConnectionInterface.php b/src/Connection/CompositeConnectionInterface.php index 9ec18a78..dc24844d 100644 --- a/src/Connection/CompositeConnectionInterface.php +++ b/src/Connection/CompositeConnectionInterface.php @@ -42,7 +42,7 @@ interface CompositeConnectionInterface extends NodeConnectionInterface /** * Reads a line from the connection. * - * @param string + * @return string */ public function readLine(); } diff --git a/src/Connection/ParametersInterface.php b/src/Connection/ParametersInterface.php index 094c57e0..25c7e1ae 100644 --- a/src/Connection/ParametersInterface.php +++ b/src/Connection/ParametersInterface.php @@ -19,18 +19,18 @@ namespace Predis\Connection; * each connection backend class (please refer to their specific documentation), * but the most common parameters used through the library are: * - * @property string scheme Connection scheme, such as 'tcp' or 'unix'. - * @property string host IP address or hostname of Redis. - * @property int port TCP port on which Redis is listening to. - * @property string path Path of a UNIX domain socket file. - * @property string alias Alias for the connection. - * @property float timeout Timeout for the connect() operation. - * @property float read_write_timeout Timeout for read() and write() operations. - * @property bool async_connect Performs the connect() operation asynchronously. - * @property bool tcp_nodelay Toggles the Nagle's algorithm for coalescing. - * @property bool persistent Leaves the connection open after a GC collection. - * @property string password Password to access Redis (see the AUTH command). - * @property string database Database index (see the SELECT command). + * @property string $scheme Connection scheme, such as 'tcp' or 'unix'. + * @property string $host IP address or hostname of Redis. + * @property int $port TCP port on which Redis is listening to. + * @property string $path Path of a UNIX domain socket file. + * @property string $alias Alias for the connection. + * @property float $timeout Timeout for the connect() operation. + * @property float $read_write_timeout Timeout for read() and write() operations. + * @property bool $async_connect Performs the connect() operation asynchronously. + * @property bool $tcp_nodelay Toggles the Nagle's algorithm for coalescing. + * @property bool $persistent Leaves the connection open after a GC collection. + * @property string $password Password to access Redis (see the AUTH command). + * @property string $database Database index (see the SELECT command). */ interface ParametersInterface { diff --git a/src/Connection/PhpiredisSocketConnection.php b/src/Connection/PhpiredisSocketConnection.php index c84b5bee..db2aeb73 100644 --- a/src/Connection/PhpiredisSocketConnection.php +++ b/src/Connection/PhpiredisSocketConnection.php @@ -331,7 +331,7 @@ class PhpiredisSocketConnection extends AbstractConnection $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { - $this->onConnectionError("`{$command->getId()}` failed: $response", 0); + $this->onConnectionError("`{$command->getId()}` failed: {$response->getMessage()}", 0); } } } diff --git a/src/Connection/Replication/MasterSlaveReplication.php b/src/Connection/Replication/MasterSlaveReplication.php index 39142c10..9d57a5ac 100644 --- a/src/Connection/Replication/MasterSlaveReplication.php +++ b/src/Connection/Replication/MasterSlaveReplication.php @@ -188,9 +188,7 @@ class MasterSlaveReplication implements ReplicationInterface */ public function getConnectionById($id) { - if (isset($this->pool[$id])) { - return $this->pool[$id]; - } + return $this->pool[$id] ?? null; } /** @@ -202,9 +200,7 @@ class MasterSlaveReplication implements ReplicationInterface */ public function getConnectionByAlias($alias) { - if (isset($this->aliases[$alias])) { - return $this->aliases[$alias]; - } + return $this->aliases[$alias] ?? null; } /** @@ -318,15 +314,15 @@ class MasterSlaveReplication implements ReplicationInterface /** * Returns a random slave. * - * @return ?NodeConnectionInterface + * @return NodeConnectionInterface|null */ protected function pickSlave() { - if ($this->slaves) { - return $this->slaves[array_rand($this->slaves)]; + if (!$this->slaves) { + return null; } - return null; + return $this->slaves[array_rand($this->slaves)]; } /** @@ -395,19 +391,21 @@ class MasterSlaveReplication implements ReplicationInterface throw new ClientException('Discovery requires a connection factory'); } - RETRY_FETCH: + while (true) { try { if ($connection = $this->getMaster()) { $this->discoverFromMaster($connection, $this->connectionFactory); + break; } elseif ($connection = $this->pickSlave()) { $this->discoverFromSlave($connection, $this->connectionFactory); + break; } else { throw new ClientException('No connection available for discovery'); } } catch (ConnectionException $exception) { $this->remove($connection); - goto RETRY_FETCH; } + } } /** @@ -478,7 +476,7 @@ class MasterSlaveReplication implements ReplicationInterface */ private function retryCommandOnFailure(CommandInterface $command, $method) { - RETRY_COMMAND: + while (true) { try { $connection = $this->getConnectionByCommand($command); $response = $connection->$method($command); @@ -486,6 +484,8 @@ class MasterSlaveReplication implements ReplicationInterface if ($response instanceof ResponseErrorInterface && $response->getErrorType() === 'LOADING') { throw new ConnectionException($connection, "Redis is loading the dataset in memory [$connection]"); } + + break; } catch (ConnectionException $exception) { $connection = $exception->getConnection(); $connection->disconnect(); @@ -507,17 +507,14 @@ class MasterSlaveReplication implements ReplicationInterface } elseif ($this->autoDiscovery) { $this->discover(); } - - goto RETRY_COMMAND; } catch (MissingMasterException $exception) { if ($this->autoDiscovery) { $this->discover(); } else { throw $exception; } - - goto RETRY_COMMAND; } + } return $response; } diff --git a/src/Connection/Replication/SentinelReplication.php b/src/Connection/Replication/SentinelReplication.php index 896c7d9c..89fe0481 100644 --- a/src/Connection/Replication/SentinelReplication.php +++ b/src/Connection/Replication/SentinelReplication.php @@ -205,8 +205,9 @@ class SentinelReplication implements ReplicationInterface public function add(NodeConnectionInterface $connection) { $parameters = $connection->getParameters(); + $role = $parameters->role; - if ('master' === $role = $parameters->role) { + if ('master' === $role) { $this->master = $connection; } elseif ('sentinel' === $role) { $this->sentinels[] = $connection; @@ -591,9 +592,7 @@ class SentinelReplication implements ReplicationInterface */ public function getConnectionById($id) { - if (isset($this->pool[$id])) { - return $this->pool[$id]; - } + return $this->pool[$id] ?? null; } /** @@ -611,6 +610,8 @@ class SentinelReplication implements ReplicationInterface return $this->pickSlave(); } elseif ($role === 'sentinel') { return $this->getSentinelConnection(); + } else { + return null; } } @@ -704,21 +705,21 @@ class SentinelReplication implements ReplicationInterface { $retries = 0; - SENTINEL_RETRY: { + while ($retries <= $this->retryLimit) { try { $response = $this->getConnectionByCommand($command)->$method($command); + break; } catch (CommunicationException $exception) { $this->wipeServerList(); $exception->getConnection()->disconnect(); - if ($retries == $this->retryLimit) { + if ($retries === $this->retryLimit) { throw $exception; } usleep($this->retryWait * 1000); ++$retries; - goto SENTINEL_RETRY; } } diff --git a/src/Connection/StreamConnection.php b/src/Connection/StreamConnection.php index d68845c4..44025306 100644 --- a/src/Connection/StreamConnection.php +++ b/src/Connection/StreamConnection.php @@ -201,7 +201,7 @@ class StreamConnection extends AbstractConnection return $resource; } - if (is_array($parameters->ssl)) { + if (isset($parameters->ssl) && is_array($parameters->ssl)) { $options = $parameters->ssl; } else { $options = []; @@ -232,7 +232,7 @@ class StreamConnection extends AbstractConnection $response = $this->executeCommand($command); if ($response instanceof ErrorResponseInterface) { - $this->onConnectionError("`{$command->getId()}` failed: $response", 0); + $this->onConnectionError("`{$command->getId()}` failed: {$response->getMessage()}", 0); } } } diff --git a/src/PubSub/AbstractConsumer.php b/src/PubSub/AbstractConsumer.php index c5c39c94..f764693d 100644 --- a/src/PubSub/AbstractConsumer.php +++ b/src/PubSub/AbstractConsumer.php @@ -58,7 +58,7 @@ abstract class AbstractConsumer implements Iterator /** * Subscribes to the specified channels. * - * @param mixed $channel,... One or more channel names. + * @param string ...$channel One or more channel names. */ public function subscribe($channel /* , ... */) { @@ -69,9 +69,9 @@ abstract class AbstractConsumer implements Iterator /** * Unsubscribes from the specified channels. * - * @param string ... One or more channel names. + * @param string ...$channel One or more channel names. */ - public function unsubscribe(/* ... */) + public function unsubscribe(...$channel) { $this->writeRequest(self::UNSUBSCRIBE, func_get_args()); } @@ -79,9 +79,9 @@ abstract class AbstractConsumer implements Iterator /** * Subscribes to the specified channels using a pattern. * - * @param mixed $pattern,... One or more channel name patterns. + * @param string ...$pattern One or more channel name patterns. */ - public function psubscribe($pattern /* ... */) + public function psubscribe(...$pattern) { $this->writeRequest(self::PSUBSCRIBE, func_get_args()); $this->statusFlags |= self::STATUS_PSUBSCRIBED; @@ -90,9 +90,9 @@ abstract class AbstractConsumer implements Iterator /** * Unsubscribes from the specified channels using a pattern. * - * @param string ... One or more channel name patterns. + * @param string ...$pattern One or more channel name patterns. */ - public function punsubscribe(/* ... */) + public function punsubscribe(...$pattern) { $this->writeRequest(self::PUNSUBSCRIBE, func_get_args()); }