mirror of
https://github.com/predis/predis.git
synced 2026-08-24 14:49:32 +00:00
fdf63bc03d
Achieving compatibility actually required a few marginal changes:
- HHVM still has some issues with re-entrant calls to __get(). The
applied change is an hack simply because it is ugly, but it is not
wrong and does not break the signature of the options interface.
- Since we cannot rely on the PHP version to detect the availability
of socket_import_stream(), we switched to function_exists(). As an
added bonus, using function_exists() is twice faster.
- In the test suite we removed an assertion for the message of an
E_WARNING simply because HHVM emits a different message. Checking
for the warning is actually enough in that context.
While the whole test suite passes on HHVM 2.3.0, please remember that
HHVM is still in development and things could break anytime especially
in some obscure corner cases.
267 lines
7.4 KiB
PHP
267 lines
7.4 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the Predis package.
|
|
*
|
|
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Predis\Connection;
|
|
|
|
use Predis\Command\CommandInterface;
|
|
use Predis\Response\Error as ErrorResponse;
|
|
use Predis\Response\Status as StatusResponse;
|
|
|
|
/**
|
|
* Standard connection to Redis servers implemented on top of PHP's streams.
|
|
* The connection parameters supported by this class are:
|
|
*
|
|
* - scheme: it can be either 'tcp' or 'unix'.
|
|
* - host: hostname or IP address of the server.
|
|
* - port: TCP port of the server.
|
|
* - path: path of a UNIX domain socket when scheme is 'unix'.
|
|
* - timeout: timeout to perform the connection.
|
|
* - read_write_timeout: timeout of read / write operations.
|
|
* - async_connect: performs the connection asynchronously.
|
|
* - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
|
|
* - persistent: the connection is left intact after a GC collection.
|
|
*
|
|
* @author Daniele Alessandri <suppakilla@gmail.com>
|
|
*/
|
|
class StreamConnection extends AbstractConnection
|
|
{
|
|
/**
|
|
* Disconnects from the server and destroys the underlying resource when the
|
|
* garbage collector kicks in only if the connection has not been marked as
|
|
* persistent.
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
if (isset($this->parameters->persistent) && $this->parameters->persistent) {
|
|
return;
|
|
}
|
|
|
|
$this->disconnect();
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
protected function createResource()
|
|
{
|
|
$initializer = "{$this->parameters->scheme}StreamInitializer";
|
|
$resource = $this->$initializer($this->parameters);
|
|
|
|
return $resource;
|
|
}
|
|
|
|
/**
|
|
* Initializes a TCP stream resource.
|
|
*
|
|
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
|
* @return resource
|
|
*/
|
|
private function tcpStreamInitializer(ParametersInterface $parameters)
|
|
{
|
|
$uri = "tcp://{$parameters->host}:{$parameters->port}";
|
|
$flags = STREAM_CLIENT_CONNECT;
|
|
|
|
if (isset($parameters->async_connect) && $parameters->async_connect) {
|
|
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
|
|
}
|
|
|
|
if (isset($parameters->persistent) && $parameters->persistent) {
|
|
$flags |= STREAM_CLIENT_PERSISTENT;
|
|
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
|
|
}
|
|
|
|
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
|
|
|
|
if (!$resource) {
|
|
$this->onConnectionError(trim($errstr), $errno);
|
|
}
|
|
|
|
if (isset($parameters->read_write_timeout)) {
|
|
$rwtimeout = $parameters->read_write_timeout;
|
|
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
|
|
$timeoutSeconds = floor($rwtimeout);
|
|
$timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
|
|
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
|
|
}
|
|
|
|
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
|
|
$socket = socket_import_stream($resource);
|
|
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
|
|
}
|
|
|
|
return $resource;
|
|
}
|
|
|
|
/**
|
|
* Initializes a UNIX stream resource.
|
|
*
|
|
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
|
* @return resource
|
|
*/
|
|
private function unixStreamInitializer(ParametersInterface $parameters)
|
|
{
|
|
$uri = "unix://{$parameters->path}";
|
|
$flags = STREAM_CLIENT_CONNECT;
|
|
|
|
if ($parameters->persistent) {
|
|
$flags |= STREAM_CLIENT_PERSISTENT;
|
|
}
|
|
|
|
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
|
|
|
|
if (!$resource) {
|
|
$this->onConnectionError(trim($errstr), $errno);
|
|
}
|
|
|
|
return $resource;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function connect()
|
|
{
|
|
parent::connect();
|
|
|
|
if ($this->initCommands) {
|
|
foreach ($this->initCommands as $command) {
|
|
$this->executeCommand($command);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function disconnect()
|
|
{
|
|
if ($this->isConnected()) {
|
|
fclose($this->getResource());
|
|
parent::disconnect();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a write operation over the stream of the buffer containing a
|
|
* command serialized with the Redis wire protocol.
|
|
*
|
|
* @param string $buffer Representation of a command in the Redis wire protocol.
|
|
*/
|
|
protected function write($buffer)
|
|
{
|
|
$socket = $this->getResource();
|
|
|
|
while (($length = strlen($buffer)) > 0) {
|
|
$written = fwrite($socket, $buffer);
|
|
|
|
if ($length === $written) {
|
|
return;
|
|
}
|
|
|
|
if ($written === false || $written === 0) {
|
|
$this->onConnectionError('Error while writing bytes to the server.');
|
|
}
|
|
|
|
$buffer = substr($buffer, $written);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function read()
|
|
{
|
|
$socket = $this->getResource();
|
|
$chunk = fgets($socket);
|
|
|
|
if ($chunk === false || $chunk === '') {
|
|
$this->onConnectionError('Error while reading line from the server.');
|
|
}
|
|
|
|
$prefix = $chunk[0];
|
|
$payload = substr($chunk, 1, -2);
|
|
|
|
switch ($prefix) {
|
|
case '+':
|
|
return StatusResponse::get($payload);
|
|
|
|
case '$':
|
|
$size = (int) $payload;
|
|
|
|
if ($size === -1) {
|
|
return null;
|
|
}
|
|
|
|
$bulkData = '';
|
|
$bytesLeft = ($size += 2);
|
|
|
|
do {
|
|
$chunk = fread($socket, min($bytesLeft, 4096));
|
|
|
|
if ($chunk === false || $chunk === '') {
|
|
$this->onConnectionError('Error while reading bytes from the server.');
|
|
}
|
|
|
|
$bulkData .= $chunk;
|
|
$bytesLeft = $size - strlen($bulkData);
|
|
} while ($bytesLeft > 0);
|
|
|
|
return substr($bulkData, 0, -2);
|
|
|
|
case '*':
|
|
$count = (int) $payload;
|
|
|
|
if ($count === -1) {
|
|
return null;
|
|
}
|
|
|
|
$multibulk = array();
|
|
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$multibulk[$i] = $this->read();
|
|
}
|
|
|
|
return $multibulk;
|
|
|
|
case ':':
|
|
return (int) $payload;
|
|
|
|
case '-':
|
|
return new ErrorResponse($payload);
|
|
|
|
default:
|
|
$this->onProtocolError("Unknown response prefix: '$prefix'.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function writeRequest(CommandInterface $command)
|
|
{
|
|
$commandID = $command->getId();
|
|
$arguments = $command->getArguments();
|
|
|
|
$cmdlen = strlen($commandID);
|
|
$reqlen = count($arguments) + 1;
|
|
|
|
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
|
|
|
for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
|
|
$argument = $arguments[$i];
|
|
$arglen = strlen($argument);
|
|
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
|
}
|
|
|
|
$this->write($buffer);
|
|
}
|
|
}
|