mirror of
https://github.com/predis/predis.git
synced 2026-08-18 18:51:46 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deee2b6d60 | |||
| bb8cce7bcf | |||
| dc14604f3e | |||
| b000013f22 | |||
| d6da14daa8 | |||
| acb2460f4c | |||
| 213f00042f | |||
| ab0c46332c | |||
| ed19ec3483 | |||
| 90496e76b5 | |||
| 4231c35e09 | |||
| a2fb02d738 | |||
| ca1f398f7c | |||
| 5e07be785a | |||
| b3cd02e2ba | |||
| c50c3393bb | |||
| 4aa3014bb0 | |||
| 92adf32e88 | |||
| c4560304cf | |||
| cf5c118a07 | |||
| bb9a670c83 | |||
| 3e56e880e8 | |||
| 1c71a4e36e | |||
| 5004f5d6fc | |||
| e616f85679 | |||
| 9cd093be24 | |||
| 305d167b08 | |||
| 3271e1edcb | |||
| b240daa106 | |||
| 8fee6ce8c3 | |||
| 35dd44b222 | |||
| 545177f09f | |||
| 2f9337cb9b | |||
| 437d80a7a3 | |||
| 3a14a39d92 | |||
| 5c9457cc2e | |||
| c551fc21f9 | |||
| 0d84606723 | |||
| c021e380d6 | |||
| 5f4b87080f | |||
| 5ae4ac6b49 | |||
| 521ac8f0eb | |||
| fbbef2a0a2 | |||
| 9930e933c6 | |||
| 16957f3b39 |
@@ -11,3 +11,5 @@
|
||||
/.travis.yml export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/phpunit.xml.travisci export-ignore
|
||||
/CHANGELOG.md export-ignore
|
||||
/CONTRIBUTING.md export-ignore
|
||||
|
||||
+9
-8
@@ -1,18 +1,21 @@
|
||||
language: php
|
||||
sudo: false
|
||||
|
||||
branches:
|
||||
except:
|
||||
- v0.5
|
||||
- v0.6
|
||||
- v0.6-PHP_5.2
|
||||
- documentation
|
||||
- main
|
||||
- v2.0
|
||||
|
||||
before_install:
|
||||
- docker run -d --rm -p 127.0.0.1:6379:6379 redis:3
|
||||
|
||||
before_script:
|
||||
- phpenv config-rm xdebug.ini
|
||||
- composer self-update
|
||||
- composer install --no-interaction --prefer-source --dev
|
||||
|
||||
script:
|
||||
- travis_retry vendor/bin/phpunit -c phpunit.xml.travisci
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
@@ -30,6 +33,4 @@ matrix:
|
||||
- php: 7.2
|
||||
- php: 7.3
|
||||
- php: 7.4
|
||||
- php: nightly # PHP 8.0.0-dev
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- php: 8.0
|
||||
|
||||
+41
-2
@@ -1,7 +1,46 @@
|
||||
v1.1.10 (2022-01-05)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: Avoid PHP 8.1 deprecation notices in `Session/Handler`
|
||||
- __FIX__: Fixed "Cannot use object of type Predis\Response\Error as array"
|
||||
error in `Connection/Aggregate/SentinelReplication`
|
||||
|
||||
|
||||
v1.1.9 (2021-10-05)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: Fixed PHP 8.1 compatibility in `StreamConnection`
|
||||
|
||||
|
||||
v1.1.8 (2021-09-29)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: Ensure compatibility with PHP 8.1.
|
||||
|
||||
|
||||
v1.1.7 (2021-04-04)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: with the phpiredis-based connection backends, failed pipelines led to
|
||||
spurious responses returned after reconnecting to Redis because the underlying
|
||||
reader was not properly reset by discarding buffered replies after disconnecting
|
||||
(ISSUE #363).
|
||||
- __FIX__: removed `cweagans/composer-patches` dev dependency and apply patches
|
||||
using `post-update-cmd` script.
|
||||
|
||||
|
||||
v1.1.6 (2020-09-11)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: reverted support for sentinels authentication implemented in v1.1.5
|
||||
as it was bugged (see ISSUE #658), sorry for the trouble. This is now postponed
|
||||
as it requires a more thorough investigation.
|
||||
|
||||
|
||||
v1.1.5 (2020-09-10)
|
||||
================================================================================
|
||||
|
||||
- __FIX__: authentication for sentinels is now supported, previously it was not
|
||||
- __FIX__:~~authentication for sentinels is now supported, previously it was not
|
||||
possible to specify a `password` for sentinels as its value was stripped during
|
||||
initialization because sentinels did not support authentication until Redis 5.
|
||||
**Please note** that with the current implementation each sentinel must have
|
||||
@@ -9,7 +48,7 @@ its own `password` parameter set in the parameters list despite this password is
|
||||
the same for all sentinels (read how `requirepass` works on the Redis docs). In
|
||||
this case you should avoid using the global `parameters` client option used to
|
||||
set default parameters for every connection created by Predis as this would end
|
||||
up using the same password even when connecting to actual Redis nodes.
|
||||
up using the same password even when connecting to actual Redis nodes.~~
|
||||
|
||||
- __FIX__: the username is now correctly retrieved from the userinfo fragment of
|
||||
the URI when using the "redis" scheme and a "username:password" pair is present.
|
||||
|
||||
+4
-14
@@ -31,8 +31,7 @@
|
||||
"php": ">=5.3.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8",
|
||||
"cweagans/composer-patches": "^1.6"
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
|
||||
@@ -43,16 +42,7 @@
|
||||
"Predis\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"composer-exit-on-patch-failure": true,
|
||||
"patches": {
|
||||
"phpunit/phpunit-mock-objects": {
|
||||
"Fix PHP 7 and 8 compatibility": "./tests/phpunit_mock_objects.patch"
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"Fix PHP 7 compatibility": "./tests/phpunit_php7.patch",
|
||||
"Fix PHP 8 compatibility": "./tests/phpunit_php8.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
"scripts": {
|
||||
"post-update-cmd": "@php -f tests/apply-patches.php"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ name = "Predis"
|
||||
desc = "Flexible and feature-complete Redis client for PHP and HHVM"
|
||||
homepage = "http://github.com/nrk/predis"
|
||||
license = "MIT"
|
||||
version = "1.1.6"
|
||||
version = "1.1.11"
|
||||
stability = "devel"
|
||||
channel = "pear.nrk.io"
|
||||
|
||||
|
||||
@@ -36,10 +36,6 @@
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<logging>
|
||||
<log type="coverage-text" target="php://stdout" />
|
||||
</logging>
|
||||
|
||||
<php>
|
||||
<!-- Redis -->
|
||||
<const name="REDIS_SERVER_VERSION" value="3.2" />
|
||||
|
||||
+3
-2
@@ -40,7 +40,7 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
|
||||
*/
|
||||
class Client implements ClientInterface, \IteratorAggregate
|
||||
{
|
||||
const VERSION = '1.1.6-dev';
|
||||
const VERSION = '1.1.11-dev';
|
||||
|
||||
protected $connection;
|
||||
protected $options;
|
||||
@@ -527,8 +527,9 @@ class Client implements ClientInterface, \IteratorAggregate
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return \Traversable<string, static>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
$clients = array();
|
||||
|
||||
@@ -92,7 +92,7 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
{
|
||||
$options = array();
|
||||
|
||||
if (strlen($this->match) > 0) {
|
||||
if (strlen(strval($this->match)) > 0) {
|
||||
$options['MATCH'] = $this->match;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,9 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
@@ -147,7 +149,9 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -155,7 +159,9 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -163,7 +169,9 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
tryFetch: {
|
||||
@@ -183,7 +191,9 @@ abstract class CursorBasedIterator implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -127,7 +127,9 @@ class ListKey implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
$this->reset();
|
||||
@@ -136,7 +138,9 @@ class ListKey implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -144,7 +148,9 @@ class ListKey implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -152,7 +158,9 @@ class ListKey implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
@@ -168,7 +176,9 @@ class ListKey implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class Command implements CommandInterface
|
||||
*/
|
||||
public static function normalizeArguments(array $arguments)
|
||||
{
|
||||
if (count($arguments) === 1 && is_array($arguments[0])) {
|
||||
if (count($arguments) === 1 && isset($arguments[0]) && is_array($arguments[0])) {
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,13 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
if ($command instanceof PrefixableCommandInterface) {
|
||||
$command->prefixKeys($this->prefix);
|
||||
} elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
|
||||
call_user_func($this->commands[$commandID], $command, $this->prefix);
|
||||
$callable = $this->commands[$commandID];
|
||||
|
||||
if (is_string($callable) && 0 === strpos($callable, 'static::')) {
|
||||
$callable = substr_replace($callable, get_class($this), 0, 6);
|
||||
}
|
||||
|
||||
call_user_func($callable, $command, $this->prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
/**
|
||||
* Returns an iterator over the list of command processor in the chain.
|
||||
*
|
||||
* @return \ArrayIterator
|
||||
* @return \Traversable<int, ProcessorInterface>
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
@@ -89,8 +89,9 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($index)
|
||||
{
|
||||
return isset($this->processors[$index]);
|
||||
@@ -98,7 +99,9 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return ProcessorInterface|null
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($index)
|
||||
{
|
||||
return $this->processors[$index];
|
||||
@@ -106,7 +109,9 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($index, $processor)
|
||||
{
|
||||
if (!$processor instanceof ProcessorInterface) {
|
||||
@@ -121,7 +126,9 @@ class ProcessorChain implements \ArrayAccess, ProcessorInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($index)
|
||||
{
|
||||
unset($this->processors[$index]);
|
||||
|
||||
@@ -31,7 +31,10 @@ class ServerSentinel extends Command
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
switch (strtolower($this->getArgument(0))) {
|
||||
$argument = $this->getArgument(0);
|
||||
$argument = is_null($argument) ? null : strtolower($argument);
|
||||
|
||||
switch ($argument) {
|
||||
case 'masters':
|
||||
case 'slaves':
|
||||
return self::processMastersOrSlaves($data);
|
||||
|
||||
@@ -34,7 +34,12 @@ abstract class CommunicationException extends PredisException
|
||||
$code = null,
|
||||
\Exception $innerException = null
|
||||
) {
|
||||
parent::__construct($message, $code, $innerException);
|
||||
parent::__construct(
|
||||
is_null($message) ? '' : $message,
|
||||
is_null($code) ? 0 : $code,
|
||||
$innerException
|
||||
);
|
||||
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
|
||||
@@ -176,16 +176,18 @@ class PredisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return \Traversable<string|int, NodeConnectionInterface>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->pool);
|
||||
|
||||
@@ -600,16 +600,18 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return count($this->pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return \Traversable<int, NodeConnectionInterface>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator()
|
||||
{
|
||||
if ($this->useClusterSlots) {
|
||||
|
||||
@@ -20,6 +20,7 @@ use Predis\Connection\NodeConnectionInterface;
|
||||
use Predis\Connection\Parameters;
|
||||
use Predis\Replication\ReplicationStrategy;
|
||||
use Predis\Replication\RoleException;
|
||||
use Predis\Response\Error;
|
||||
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
||||
use Predis\Response\ServerException;
|
||||
|
||||
@@ -146,14 +147,14 @@ class SentinelReplication implements ReplicationInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time to wait (in seconds) before fetching a new configuration
|
||||
* Sets the time to wait (in milliseconds) before fetching a new configuration
|
||||
* from one of the sentinels.
|
||||
*
|
||||
* @param float $seconds Time to wait before the next attempt.
|
||||
* @param float $milliseconds Time to wait before the next attempt.
|
||||
*/
|
||||
public function setRetryWait($seconds)
|
||||
public function setRetryWait($milliseconds)
|
||||
{
|
||||
$this->retryWait = (float) $seconds;
|
||||
$this->retryWait = (float) $milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,12 +240,15 @@ class SentinelReplication implements ReplicationInterface
|
||||
}
|
||||
|
||||
if (is_array($parameters)) {
|
||||
// Password authentication is fine now that Redis Sentinel supports
|
||||
// password-protected sentinel instances, but we must explicitly set
|
||||
// "database" and "username" to NULL so that no augmented AUTH (ACL)
|
||||
// and SELECT command are sent by accident to the sentinels.
|
||||
// NOTE: sentinels do not accept AUTH and SELECT commands so we must
|
||||
// explicitly set them to NULL to avoid problems when using default
|
||||
// parameters set via client options. Actually AUTH is supported for
|
||||
// sentinels starting with Redis 5 but we have to differentiate from
|
||||
// sentinels passwords and nodes passwords, this will be implemented
|
||||
// in a later release.
|
||||
$parameters['database'] = null;
|
||||
$parameters['username'] = null;
|
||||
$parameters['password'] = null;
|
||||
|
||||
if (!isset($parameters['timeout'])) {
|
||||
$parameters['timeout'] = $this->sentinelTimeout;
|
||||
@@ -520,13 +524,17 @@ class SentinelReplication implements ReplicationInterface
|
||||
* @param NodeConnectionInterface $connection Connection to a redis server.
|
||||
* @param string $role Expected role of the server ("master", "slave" or "sentinel").
|
||||
*
|
||||
* @throws RoleException
|
||||
* @throws RoleException|ConnectionException
|
||||
*/
|
||||
protected function assertConnectionRole(NodeConnectionInterface $connection, $role)
|
||||
{
|
||||
$role = strtolower($role);
|
||||
$actualRole = $connection->executeCommand(RawCommand::create('ROLE'));
|
||||
|
||||
if ($actualRole instanceof Error) {
|
||||
throw new ConnectionException($connection, $actualRole->getMessage());
|
||||
}
|
||||
|
||||
if ($role !== $actualRole[0]) {
|
||||
throw new RoleException($connection, "Expected $role but got $actualRole[0] [$connection]");
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
|
||||
parent::__destruct();
|
||||
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,9 +227,7 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
$protocol = SOL_TCP;
|
||||
}
|
||||
|
||||
$socket = @socket_create($domain, SOCK_STREAM, $protocol);
|
||||
|
||||
if (!is_resource($socket)) {
|
||||
if (false === $socket = @socket_create($domain, SOCK_STREAM, $protocol)) {
|
||||
$this->emitSocketError();
|
||||
}
|
||||
|
||||
@@ -344,7 +342,9 @@ class PhpiredisSocketConnection extends AbstractConnection
|
||||
public function disconnect()
|
||||
{
|
||||
if ($this->isConnected()) {
|
||||
phpiredis_reader_reset($this->reader);
|
||||
socket_close($this->getResource());
|
||||
|
||||
parent::disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,19 @@ class PhpiredisStreamConnection extends StreamConnection
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
|
||||
parent::__destruct();
|
||||
|
||||
phpiredis_reader_destroy($this->reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
phpiredis_reader_reset($this->reader);
|
||||
|
||||
parent::disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,8 +123,9 @@ class PhpiredisStreamConnection extends StreamConnection
|
||||
{
|
||||
$socket = null;
|
||||
$timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
|
||||
$context = stream_context_create();
|
||||
|
||||
$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags);
|
||||
$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context);
|
||||
|
||||
if (!$resource) {
|
||||
$this->onConnectionError(trim($errstr), $errno);
|
||||
|
||||
@@ -123,8 +123,9 @@ class StreamConnection extends AbstractConnection
|
||||
protected function createStreamSocket(ParametersInterface $parameters, $address, $flags)
|
||||
{
|
||||
$timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
|
||||
$context = stream_context_create();
|
||||
|
||||
if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags)) {
|
||||
if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context)) {
|
||||
$this->onConnectionError(trim($errstr), $errno);
|
||||
}
|
||||
|
||||
@@ -288,7 +289,7 @@ class StreamConnection extends AbstractConnection
|
||||
$socket = $this->getResource();
|
||||
|
||||
while (($length = strlen($buffer)) > 0) {
|
||||
$written = @fwrite($socket, $buffer);
|
||||
$written = is_resource($socket) ? @fwrite($socket, $buffer) : false;
|
||||
|
||||
if ($length === $written) {
|
||||
return;
|
||||
@@ -332,7 +333,7 @@ class StreamConnection extends AbstractConnection
|
||||
$bytesLeft = ($size += 2);
|
||||
|
||||
do {
|
||||
$chunk = fread($socket, min($bytesLeft, 4096));
|
||||
$chunk = is_resource($socket) ? fread($socket, min($bytesLeft, 4096)) : false;
|
||||
|
||||
if ($chunk === false || $chunk === '') {
|
||||
$this->onConnectionError('Error while reading bytes from the server.');
|
||||
@@ -387,7 +388,7 @@ class StreamConnection extends AbstractConnection
|
||||
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
$arglen = strlen($argument);
|
||||
$arglen = strlen(strval($argument));
|
||||
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class Consumer implements \Iterator
|
||||
{
|
||||
private $client;
|
||||
private $valid;
|
||||
private $position;
|
||||
private $position = 0;
|
||||
|
||||
/**
|
||||
* @param ClientInterface $client Client instance used by the consumer.
|
||||
@@ -90,7 +90,9 @@ class Consumer implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -101,6 +103,7 @@ class Consumer implements \Iterator
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->getValue();
|
||||
@@ -108,7 +111,9 @@ class Consumer implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -116,7 +121,9 @@ class Consumer implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
++$this->position;
|
||||
@@ -127,6 +134,7 @@ class Consumer implements \Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->valid;
|
||||
|
||||
@@ -225,6 +225,19 @@ class Pipeline implements ClientContextInterface
|
||||
return $this->responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the buffer holding all of the commands and responses.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->responses = array();
|
||||
$this->pipeline = new \SplQueue();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the pipeline should throw exceptions on server errors.
|
||||
*
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
const STATUS_SUBSCRIBED = 2; // 0b0010
|
||||
const STATUS_PSUBSCRIBED = 4; // 0b0100
|
||||
|
||||
private $position = null;
|
||||
private $position = 0;
|
||||
private $statusFlags = self::STATUS_VALID;
|
||||
|
||||
/**
|
||||
@@ -150,7 +150,9 @@ abstract class AbstractConsumer implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -160,8 +162,9 @@ abstract class AbstractConsumer implements \Iterator
|
||||
* Returns the last message payload retrieved from the server and generated
|
||||
* by one of the active subscriptions.
|
||||
*
|
||||
* @return array
|
||||
* @return \stdClass
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->getValue();
|
||||
@@ -169,7 +172,9 @@ abstract class AbstractConsumer implements \Iterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -178,6 +183,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if ($this->valid()) {
|
||||
@@ -192,6 +198,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
$isValid = $this->isFlagSet(self::STATUS_VALID);
|
||||
@@ -213,7 +220,7 @@ abstract class AbstractConsumer implements \Iterator
|
||||
* Waits for a new message from the server generated by one of the active
|
||||
* subscriptions and returns it when available.
|
||||
*
|
||||
* @return array
|
||||
* @return \stdClass
|
||||
*/
|
||||
abstract protected function getValue();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ use Predis\ClientInterface;
|
||||
use Predis\Command\Command;
|
||||
use Predis\Connection\AggregateConnectionInterface;
|
||||
use Predis\NotSupportedException;
|
||||
use Predis\Response\ServerException;
|
||||
use Predis\Response\Error;
|
||||
|
||||
/**
|
||||
* PUB/SUB consumer abstraction.
|
||||
@@ -116,6 +118,10 @@ class Consumer extends AbstractConsumer
|
||||
{
|
||||
$response = $this->client->getConnection()->read();
|
||||
|
||||
if ($response instanceof Error) {
|
||||
throw new ServerException($response->getMessage());
|
||||
}
|
||||
|
||||
switch ($response[0]) {
|
||||
case self::SUBSCRIBE:
|
||||
case self::UNSUBSCRIBE:
|
||||
|
||||
@@ -62,7 +62,8 @@ class ReplicationStrategy
|
||||
}
|
||||
|
||||
if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
|
||||
$sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
|
||||
$argument = $command->getArgument(0);
|
||||
$sha1 = $eval ? sha1(strval($argument)) : $argument;
|
||||
|
||||
if (isset($this->readonlySHA1[$sha1])) {
|
||||
if (true === $readonly = $this->readonlySHA1[$sha1]) {
|
||||
|
||||
@@ -33,7 +33,9 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind()
|
||||
{
|
||||
// NOOP
|
||||
@@ -41,7 +43,9 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->current;
|
||||
@@ -49,7 +53,9 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
@@ -57,7 +63,9 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
if (++$this->position < $this->size) {
|
||||
@@ -67,7 +75,9 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid()
|
||||
{
|
||||
return $this->position < $this->size;
|
||||
@@ -82,6 +92,7 @@ abstract class MultiBulkIterator implements \Iterator, \Countable, ResponseInter
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return $this->size;
|
||||
|
||||
@@ -60,7 +60,9 @@ class MultiBulkTuple extends MultiBulk implements \OuterIterator
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return \Iterator
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getInnerIterator()
|
||||
{
|
||||
return $this->iterator;
|
||||
|
||||
@@ -64,7 +64,9 @@ class Handler implements \SessionHandlerInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function open($save_path, $session_id)
|
||||
{
|
||||
// NOOP
|
||||
@@ -73,7 +75,9 @@ class Handler implements \SessionHandlerInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function close()
|
||||
{
|
||||
// NOOP
|
||||
@@ -82,7 +86,9 @@ class Handler implements \SessionHandlerInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int|bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
// NOOP
|
||||
@@ -91,7 +97,9 @@ class Handler implements \SessionHandlerInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function read($session_id)
|
||||
{
|
||||
if ($data = $this->client->get($session_id)) {
|
||||
@@ -102,7 +110,9 @@ class Handler implements \SessionHandlerInterface
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
$this->client->setex($session_id, $this->ttl, $session_data);
|
||||
@@ -112,7 +122,9 @@ class Handler implements \SessionHandlerInterface
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$this->client->del($session_id);
|
||||
|
||||
@@ -29,7 +29,7 @@ class AbortedMultiExecException extends PredisException
|
||||
*/
|
||||
public function __construct(MultiExec $transaction, $message, $code = null)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
parent::__construct($message, is_null($code) ? 0 : $code);
|
||||
$this->transaction = $transaction;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class ConnectionSelectTest extends PredisCommandTestCase
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage ERR invalid DB index
|
||||
* @expectedExceptionMessageRegExp /ERR (invalid DB index|value is not)/
|
||||
*/
|
||||
public function testThrowsExceptionOnUnexpectedDatabaseName()
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ class KeyMoveTest extends PredisCommandTestCase
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage ERR index out of range
|
||||
* @expectedExceptionMessageRegEx /ERR.*out of range/
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidDatabases()
|
||||
{
|
||||
|
||||
@@ -66,6 +66,10 @@ class ReplicationOptionTest extends PredisTestCase
|
||||
*/
|
||||
public function testDoesNotAcceptValuesThatCanBeInterpretedAsBooleanFalse($value)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.4', '<')) {
|
||||
$this->markTestSkipped('Skip test, because.');
|
||||
}
|
||||
|
||||
$option = new ReplicationOption();
|
||||
$options = $this->getMock('Predis\Configuration\OptionsInterface');
|
||||
|
||||
|
||||
@@ -36,29 +36,19 @@ class SentinelReplicationTest extends PredisTestCase
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParametersForSentinelConnectionShouldUsePasswordForAuthentication()
|
||||
public function testParametersForSentinelConnectionShouldNotUseDatabaseAndUsernameAndPassword()
|
||||
{
|
||||
$replication = $this->getReplicationConnection('svc', array(
|
||||
'tcp://127.0.0.1:5381?alias=sentinel1&password=secret',
|
||||
'tcp://127.0.0.1:5381?alias=sentinel1&database=1&username=myusername&password=secret',
|
||||
));
|
||||
|
||||
$parameters = $replication->getSentinelConnection()->getParameters()->toArray();
|
||||
|
||||
$this->assertArraySubset(array('password' => 'secret'), $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParametersForSentinelConnectionShouldNotUseDatabaseAndUsername()
|
||||
{
|
||||
$replication = $this->getReplicationConnection('svc', array(
|
||||
'tcp://127.0.0.1:5381?alias=sentinel1&database=1&username=myusername',
|
||||
));
|
||||
|
||||
$parameters = $replication->getSentinelConnection()->getParameters()->toArray();
|
||||
|
||||
$this->assertArraySubset(array('database' => null, 'username' => null), $parameters);
|
||||
$this->assertArraySubset(array(
|
||||
'database' => null,
|
||||
'username' => null,
|
||||
'password' => null
|
||||
), $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,6 +76,28 @@ class PhpiredisSocketConnectionTest extends PredisConnectionTestCase
|
||||
// ---- INTEGRATION TESTS --------------------------------------------- //
|
||||
// ******************************************************************** //
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
*/
|
||||
public function testClearsPendingResponsesInReaderBufferOnDisconnect()
|
||||
{
|
||||
$profile = $this->getCurrentProfile();
|
||||
$connection = $this->createConnection();
|
||||
|
||||
$cmdECHOBefore = $profile->createCommand('echo', array('BEFORE DISCONNECT'));
|
||||
$cmdECHOAfter = $profile->createCommand('echo', array('AFTER DISCONNECT'));
|
||||
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->readResponse($cmdECHOBefore);
|
||||
$connection->disconnect();
|
||||
|
||||
$response = $connection->executeCommand($cmdECHOAfter);
|
||||
|
||||
$this->assertSame('AFTER DISCONNECT', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Connection\ConnectionException
|
||||
|
||||
@@ -76,6 +76,28 @@ class PhpiredisStreamConnectionTest extends PredisConnectionTestCase
|
||||
// ---- INTEGRATION TESTS --------------------------------------------- //
|
||||
// ******************************************************************** //
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
*/
|
||||
public function testClearsPendingResponsesInReaderBufferOnDisconnect()
|
||||
{
|
||||
$profile = $this->getCurrentProfile();
|
||||
$connection = $this->createConnection();
|
||||
|
||||
$cmdECHOBefore = $profile->createCommand('echo', array('BEFORE DISCONNECT'));
|
||||
$cmdECHOAfter = $profile->createCommand('echo', array('AFTER DISCONNECT'));
|
||||
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->writeRequest($cmdECHOBefore);
|
||||
$connection->readResponse($cmdECHOBefore);
|
||||
$connection->disconnect();
|
||||
|
||||
$response = $connection->executeCommand($cmdECHOAfter);
|
||||
|
||||
$this->assertSame('AFTER DISCONNECT', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @group slow
|
||||
|
||||
@@ -234,6 +234,59 @@ class PipelineTest extends PredisTestCase
|
||||
$this->assertSame(array('one', 'two', 'three', 'four'), $pipeline->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testClearBuffer()
|
||||
{
|
||||
$connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
|
||||
$connection->expects($this->never())
|
||||
->method('writeRequest');
|
||||
$connection->expects($this->never())
|
||||
->method('readResponse')
|
||||
->will($this->returnCallback($this->getReadCallback()));
|
||||
|
||||
$pipeline = new Pipeline(new Client($connection));
|
||||
|
||||
$pipeline->echo('one');
|
||||
$pipeline->echo('two');
|
||||
|
||||
$pipeline->clear();
|
||||
|
||||
$this->assertSame(array(), $pipeline->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testClearResponses()
|
||||
{
|
||||
$connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
|
||||
$connection->expects($this->exactly(4))
|
||||
->method('writeRequest');
|
||||
$connection->expects($this->exactly(4))
|
||||
->method('readResponse')
|
||||
->will($this->returnCallback($this->getReadCallback()));
|
||||
|
||||
$pipeline = new Pipeline(new Client($connection));
|
||||
|
||||
$pipeline->echo('one');
|
||||
$pipeline->echo('two');
|
||||
|
||||
$this->assertSame(array('one', 'two'), $pipeline->execute());
|
||||
|
||||
$pipeline->clear();
|
||||
|
||||
$pipeline->echo('three');
|
||||
$pipeline->echo('four');
|
||||
|
||||
$this->assertSame(array('three', 'four'), $pipeline->execute());
|
||||
|
||||
$pipeline->clear();
|
||||
|
||||
$this->assertSame(array(), $pipeline->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
|
||||
@@ -426,4 +426,32 @@ class ConsumerTest extends PredisTestCase
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
*/
|
||||
public function testInvalidSubscriptionThrowsServerException()
|
||||
{
|
||||
$parameters = array(
|
||||
'host' => REDIS_SERVER_HOST,
|
||||
'port' => REDIS_SERVER_PORT,
|
||||
'database' => REDIS_SERVER_DBNUM,
|
||||
'read_write_timeout' => -1, // -1 to set blocking reads
|
||||
);
|
||||
|
||||
$options = array('profile' => REDIS_SERVER_VERSION);
|
||||
|
||||
$consumer = new Client($parameters, $options);
|
||||
$consumer->connect();
|
||||
|
||||
$pubsub = $consumer->pubSubLoop();
|
||||
|
||||
// calling psubscribe with invalid empty parameter list
|
||||
$pubsub->psubscribe(array());
|
||||
|
||||
foreach ($pubsub as $message) {
|
||||
// should trigger the ServerException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Apply patches to support newer PHP versions.
|
||||
*/
|
||||
|
||||
$patches = array(
|
||||
'phpunit_mock_objects.patch' => 'phpunit/phpunit-mock-objects',
|
||||
'phpunit_php7.patch' => 'phpunit/phpunit',
|
||||
'phpunit_php8.patch' => 'phpunit/phpunit',
|
||||
'phpunit_php81.patch' => 'phpunit/phpunit',
|
||||
);
|
||||
|
||||
foreach ($patches as $patch => $package) {
|
||||
chdir(__DIR__.'/../vendor/'.$package);
|
||||
passthru(sprintf('patch -p1 -f < ../../../tests/%s', $patch));
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
diff --git a/src/Util/Configuration.php b/src/Util/Configuration.php
|
||||
index b2f7a7bd0c6..1a1a444d378 100644
|
||||
--- a/src/Util/Configuration.php
|
||||
+++ b/src/Util/Configuration.php
|
||||
@@ -514,22 +514,17 @@ public function handlePHPConfiguration()
|
||||
|
||||
foreach (array('var', 'post', 'get', 'cookie', 'server', 'files', 'request') as $array) {
|
||||
// See https://github.com/sebastianbergmann/phpunit/issues/277
|
||||
- switch ($array) {
|
||||
- case 'var':
|
||||
- $target = &$GLOBALS;
|
||||
- break;
|
||||
-
|
||||
- case 'server':
|
||||
- $target = &$_SERVER;
|
||||
- break;
|
||||
-
|
||||
- default:
|
||||
- $target = &$GLOBALS['_' . strtoupper($array)];
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
foreach ($configuration[$array] as $name => $value) {
|
||||
- $target[$name] = $value;
|
||||
+ switch ($array) {
|
||||
+ case 'var':
|
||||
+ $GLOBALS[$name] = $value;
|
||||
+ break;
|
||||
+ case 'server':
|
||||
+ $_SERVER[$name] = $value;
|
||||
+ break;
|
||||
+ default:
|
||||
+ $GLOBALS['_' . strtoupper($array)][$name] = $value;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/Util/Getopt.php b/src/Util/Getopt.php
|
||||
index 370e97d73f8..5bd8553970b 100644
|
||||
--- a/src/Util/Getopt.php
|
||||
+++ b/src/Util/Getopt.php
|
||||
@@ -144,7 +144,7 @@ protected static function parseLongOption($arg, $long_options, &$opts, &$args)
|
||||
|
||||
if (substr($long_opt, -1) == '=') {
|
||||
if (substr($long_opt, -2) != '==') {
|
||||
- if (!strlen($opt_arg)) {
|
||||
+ if (null === $opt_arg || strlen($opt_arg) < 1) {
|
||||
$opt_arg = current($args);
|
||||
next($args);
|
||||
if (!($opt_arg)) {
|
||||
Reference in New Issue
Block a user