Building ReactPHP Memached Client
Building ReactPHP Memached Client
Client Factory
- a stream, which represents a binary socket connection between client and server
- some Memcached protocol parser to create requests and parse responses.
<?php
namespace seregazhuk\React\Memcached;
use React\EventLoop\LoopInterface;
use React\Socket\Connector;
class Factory
{
private $connector;
/**
* @param LoopInterface $loop
*/
public function __construct(LoopInterface $loop)
{
$this->connector = new Connector($loop);
}
// ...
}
<?php
namespace seregazhuk\React\Memcached;
use React\EventLoop\LoopInterface;
use React\Promise\PromiseInterface;
use React\Socket\ConnectionInterface;
use React\Socket\Connector;
use seregazhuk\React\Memcached\Protocol\Parser;
use seregazhuk\React\Memcached\Protocol\Response\Factory as ResponseFactory;
use seregazhuk\React\Memcached\Protocol\Request\Factory as RequestFactory;
class Factory
{
private $connector;
/**
* @param LoopInterface $loop
*/
public function __construct(LoopInterface $loop)
{
$this->connector = new Connector($loop);
}
/**
* Creates a Memcached client connected to a given connection string
*
* @param string $address Memcached server URI to connect to
* @return PromiseInterface resolves with Client or rejects with \RuntimeException
*/
public function createClient($address)
{
$promise = $this
->connector
->connect($address)
->then(
function (ConnectionInterface $stream) {
return new Client($stream, $this->createProtocolParser());
});
return $promise;
}
/**
* @return Parser
*/
private function createProtocolParser()
{
return new Parser(new RequestFactory(), new ResponseFactory());
}
}
<?php
use seregazhuk\React\Memcached\Factory;
use seregazhuk\React\Memcached\Client;
require '../vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$factory->createClient('localhost:11211')->then(
function (Client $client) {
// connection was established
// ...
},
function(Exception $e){
// something went wrong
echo 'Error connecting to server: ' . $e->getMessage();
});
$loop->run();
Client
Making requests
<?php
namespace seregazhuk\React\Memcached;
use LogicException;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
use React\Stream\DuplexStreamInterface;
use seregazhuk\React\Memcached\Protocol\Parser;
class Client
{
/**
* @var Parser
*/
protected $parser;
/**
* @var DuplexStreamInterface
*/
private $stream;
/**
* @param DuplexStreamInterface $stream
* @param Parser $parser
*/
public function __construct(DuplexStreamInterface $stream, Parser $parser)
{
$this->stream = $stream;
$this->parser = $parser;
// ...
}
}
- A promise is a placeholder for the initially unknown result of the asynchronous code.
- A deferred represents the code which is going to be executed to receive this result.
<?php
namespace seregazhuk\React\Memcached;
use React\Promise\Deferred;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
class Request
{
/**
* @var Deferred
*/
private $deferred;
/**
* @var string
*/
private $command;
/**
* @param string $command
*/
public function __construct($command)
{
$this->deferred = new Deferred();
$this->command = $command;
}
/**
* @return string
*/
public function getCommand()
{
return $this->command;
}
/**
* @return Promise|PromiseInterface
*/
public function getPromise()
{
return $this->deferred->promise();
}
/**
* @param mixed $value
*/
public function resolve($value)
{
$this->deferred->resolve($value);
}
}
<?php
class Client
{
// ...
/**
* @param string $name
* @param array $args
* @return Promise|PromiseInterface
*/
public function __call($name, $args)
{
$request = new Request($name);
$query = $this->parser->makeRequest($name, $args);
$this->stream->write($query);
$this->requests[] = $request;
return $request->getPromise();
}
}
Handling responses
<?php
class Client
{
// ...
/**
* @param DuplexStreamInterface $stream
* @param Parser $parser
*/
public function __construct(DuplexStreamInterface $stream, Parser $parser)
{
$this->stream = $stream;
$this->parser = $parser;
$stream->on('data', function ($chunk) {
// ...
});
}
}
- The protocol parser parses the raw data into a batch of responses (because we can receive responses for several commands at once).
- We resolve pending requests with these responses.
- If there are no pending requests but we have received a response, that means that something went wrong and we throw an exception.
<?php
class Client
{
// ...
/**
* @param DuplexStreamInterface $stream
* @param Parser $parser
*/
public function __construct(DuplexStreamInterface $stream, Parser $parser)
{
$this->stream = $stream;
$this->parser = $parser;
$stream->on('data', function ($chunk) {
$parsed = $this->parser->parseRawResponse($chunk);
$this->resolveRequests($parsed);
});
}
/**
* @param array $responses
* @throws LogicException
*/
protected function resolveRequests(array $responses)
{
if (empty($this->requests)) {
throw new LogicException('Received unexpected response, no matching request found');
}
foreach ($responses as $response) {
/* @var $request Request */
$request = array_shift($this->requests);
$parsedResponse = $this->parser->parseResponse($request->getCommand(), $response);
$request->resolve($parsedResponse);
}
}
}
<?php
use seregazhuk\React\Memcached\Factory;
use seregazhuk\React\Memcached\Client;
require '../vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$factory->createClient('localhost:11211')->then(
function (Client $client) {
$client->set('name', 'test')->then(function($result){
var_dump($result);
echo "The value was stored\n";
});
$client->get('name')->then(function($data){
var_dump($data);
echo "The value was retrieved\n";
});
},
function(Exception $e){
echo 'Error connecting to server: ' . $e->getMessage();
});
$loop->run();
<?php
use seregazhuk\React\Memcached\Factory;
use seregazhuk\React\Memcached\Client;
$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$factory->createClient('localhost:11211')->then(
function (Client $client) {
$client->version()->then(function($result){
echo "Memcached version: {$result}\n";
});
},
function(Exception $e){
echo 'Error connecting to server: ' . $e->getMessage();
});
$loop->run();
Comments
Post a Comment