Files
predis/lib/Predis/Network/StreamConnection.php
T

219 lines
7.3 KiB
PHP

<?php
namespace Predis\Network;
use Predis\ResponseError;
use Predis\ResponseQueued;
use Predis\ServerException;
use Predis\ConnectionParameters;
use Predis\CommunicationException;
use Predis\Commands\ICommand;
use Predis\Protocols\TextCommandSerializer;
use Predis\Iterators\MultiBulkResponseSimple;
class StreamConnection extends ConnectionBase {
private $_commandSerializer, $_mbiterable, $_throwErrors;
public function __construct(ConnectionParameters $parameters) {
parent::__construct($this->checkParameters($parameters));
$this->_commandSerializer = new TextCommandSerializer();
}
public function __destruct() {
if (!$this->_params->connection_persistent) {
$this->disconnect();
}
}
protected function checkParameters(ConnectionParameters $parameters) {
switch ($parameters->scheme) {
case 'unix':
$pathToSocket = $parameters->path;
if (!isset($pathToSocket)) {
throw new \InvalidArgumentException('Missing UNIX domain socket path');
}
if (!file_exists($pathToSocket)) {
throw new \InvalidArgumentException("Could not find $pathToSocket");
}
case 'tcp':
return $parameters;
default:
throw new \InvalidArgumentException("Invalid scheme: {$parameters->scheme}");
}
}
protected function createResource() {
$parameters = $this->_params;
$initializer = array($this, "{$parameters->scheme}StreamInitializer");
return call_user_func($initializer, $parameters);
}
private function tcpStreamInitializer(ConnectionParameters $parameters) {
$uri = sprintf('tcp://%s:%d/', $parameters->host, $parameters->port);
$flags = STREAM_CLIENT_CONNECT;
if ($parameters->connection_async) {
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
}
if ($parameters->connection_persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
}
$resource = @stream_socket_client(
$uri, $errno, $errstr, $parameters->connection_timeout, $flags
);
if (!$resource) {
$this->onCommunicationException(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);
}
return $resource;
}
private function unixStreamInitializer(ConnectionParameters $parameters) {
$uri = sprintf('unix:///%s', $parameters->path);
$flags = STREAM_CLIENT_CONNECT;
if ($parameters->connection_persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
}
$resource = @stream_socket_client(
$uri, $errno, $errstr, $parameters->connection_timeout, $flags
);
if (!$resource) {
$this->onCommunicationException(trim($errstr), $errno);
}
return $resource;
}
public function connect() {
parent::connect();
if (count($this->_initCmds) > 0){
$this->sendInitializationCommands();
}
}
public function disconnect() {
if ($this->isConnected()) {
fclose($this->getResource());
parent::disconnect();
}
}
private function sendInitializationCommands() {
foreach ($this->_initCmds as $command) {
$this->writeCommand($command);
}
foreach ($this->_initCmds as $command) {
$this->readResponse($command);
}
}
private 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->onCommunicationException(
'Error while writing bytes to the server'
);
}
$value = substr($buffer, $written);
}
}
public function read() {
$socket = $this->getResource();
$chunk = fgets($socket);
if ($chunk === false || $chunk === '') {
$this->onCommunicationException(
'Error while reading line from the server'
);
}
$prefix = $chunk[0];
$payload = substr($chunk, 1, -2);
switch ($prefix) {
case '+': // inline
switch ($payload) {
case 'OK':
return true;
case 'QUEUED':
return new ResponseQueued();
default:
return $payload;
}
case '$': // bulk
$size = (int) $payload;
if ($size === -1) {
return null;
}
$bulkData = '';
$bytesLeft = ($size += 2);
do {
$chunk = fread($socket, min($bytesLeft, 4096));
if ($chunk === false || $chunk === '') {
$this->onCommunicationException(
'Error while reading bytes from the server'
);
}
$bulkData .= $chunk;
$bytesLeft = $size - strlen($bulkData);
} while ($bytesLeft > 0);
return substr($bulkData, 0, -2);
case '*': // multi bulk
$count = (int) $payload;
if ($count === -1) {
return null;
}
if ($this->_mbiterable === true) {
return new MultiBulkResponseSimple($this, $count);
}
$multibulk = array();
for ($i = 0; $i < $count; $i++) {
$multibulk[$i] = $this->read();
}
return $multibulk;
case ':': // integer
return (int) $payload;
case '-': // error
$errorMessage = substr($payload, 4);
if ($this->_throwErrors) {
throw new ServerException($errorMessage);
}
return new ResponseError($errorMessage);
default:
$this->onCommunicationException("Unknown prefix: '$prefix'");
}
}
public function writeCommand(ICommand $command) {
$this->write($this->_commandSerializer->serialize($command));
}
public function readResponse(ICommand $command) {
$reply = $this->read();
return isset($reply->skipParse) ? $reply : $command->parseResponse($reply);
}
public function setProtocolOption($option, $value) {
switch ($option) {
case 'iterable_multibulk':
$this->_mbiterable = (bool) $value;
break;
case 'throw_errors':
$this->_throwErrors = (bool) $value;
break;
}
}
}