Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"keywords": ["socket"],
"license": "MIT",
"require": {
"php": ">=5.3.0",
"php": ">=5.5.0",
"evenement/evenement": "~2.0|~1.0",
"react/event-loop": "0.4.*|0.3.*",
"react/stream": "0.4.*|0.3.*"
Expand Down
73 changes: 73 additions & 0 deletions src/ConnectionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php


namespace React\Socket;



use React\EventLoop\LoopInterface;

class ConnectionFactory
{
/**
* @var string The default connection class to use if no entry is found in the map.
*/
protected $defaultDefinition;

/**
* @var array A mapping from stream type to definition. A definition is either a class name or a closure.
*/
protected $map;

/**
* ConnectionFactory constructor.
* @param array $map A map, mapping socket types to a connection class. The class may be given as string or closure.
* @param string|Closure $defaultDefinition A default class to use if no mapping is found.
*/
public function __construct(array $map = [], $defaultDefinition = Connection::class)
{
$this->map = $map;
$this->defaultDefinition = $defaultDefinition;
}

/**
* @inheritdoc
*/
public function createConnection($socket, LoopInterface $loop)
{
if (!is_resource($socket)) {
throw new \InvalidArgumentException("Socket must be of type resource.");
}

$meta = stream_get_meta_data($socket);
$key = $meta['stream_type'];
$definition = isset($this->map[$key]) ? $this->map[$key] : $this->defaultDefinition;
return $this->constructConnection($definition, $socket, $loop);

}

/**
* @param string|Closure $definition A class name or \Closure that constructs the desired class.
* @param $socket A PHP stream resource.
* @param LoopInterface $loop The loop to use as the second argument for the connection constructor / Closure.
* @throws \InvalidArgumentException when the definition cannot be resolved.
* @throws \UnexpectedValueException when the definition doesn't return an instance of ConnectionInterface.
*/
protected function constructConnection($definition, $socket, LoopInterface $loop)
{
if (is_string($definition) && class_exists($definition)) {
$result = new $definition($socket, $loop);
} elseif ($definition instanceof \Closure) {
$result = $definition($socket, $loop);
} else {
throw new \InvalidArgumentException("Definition must either be a class name or a closure.");
}

if (!$result instanceof ConnectionInterface) {
throw new \UnexpectedValueException("Result must implement ConnectionInterface");
}

return $result;
}

}
18 changes: 18 additions & 0 deletions src/ConnectionFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace React\Socket;


use React\EventLoop\LoopInterface;

interface ConnectionFactoryInterface
{
/**
* @param $socket The PHP stream resource.
* @param LoopInterface $loop The loop to use for constructing the connection.
* @throws \InvalidArgumentException when $socket is not of type resource.
* @return ConnectionInterface
*/
public function createConnection($socket, LoopInterface $loop);
}
3 changes: 3 additions & 0 deletions src/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@

interface ConnectionInterface extends ReadableStreamInterface, WritableStreamInterface
{
/**
* @return string The remote address for the connection.
*/
public function getRemoteAddress();
}
31 changes: 29 additions & 2 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,26 @@ class Server extends EventEmitter implements ServerInterface
public $master;
private $loop;

public function __construct(LoopInterface $loop)
/**
* @var ConnectionFactoryInterface
*/
protected $factory;

/**
* Server constructor.
* @param LoopInterface $loop
* @param ConnectionFactoryInterface|null $factory The factory to use for creating connections.
* Uses a default factory if none is passed for backwards compatibility.
*/
public function __construct(LoopInterface $loop, ConnectionFactoryInterface $factory = null)
{
$this->loop = $loop;
$this->factory = $factory ?: new ConnectionFactory();
}

/**
* @inheritdoc
*/
public function listen($port, $host = '127.0.0.1')
{
if (strpos($host, ':') !== false) {
Expand Down Expand Up @@ -43,6 +58,12 @@ public function listen($port, $host = '127.0.0.1')
});
}

/**
* Handles a new connection by configuring the socket,
* constructing the connection object and emitting the connection event.
* @todo Check if this should be protected instead of public.
* @param resource $socket
*/
public function handleConnection($socket)
{
stream_set_blocking($socket, 0);
Expand All @@ -66,8 +87,14 @@ public function shutdown()
$this->removeAllListeners();
}

/**
* Constructs a connection object given a socket.
* @todo Check if this should be protected instead of public.
* @param resource $socket The PHP stream resource.
* @return ConnectionInterface
*/
public function createConnection($socket)
{
return new Connection($socket, $this->loop);
return $this->factory->createConnection($socket, $this->loop);
}
}
13 changes: 13 additions & 0 deletions src/ServerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@
/** Emits the connection event */
interface ServerInterface extends EventEmitterInterface
{
/**
* @param int $port The port number to listen on.
* @param string $host The address to bind on.
* @return void
*/
public function listen($port, $host = '127.0.0.1');

/**
* @return int
*/
public function getPort();

/**
* @return void
*/
public function shutdown();
}
35 changes: 35 additions & 0 deletions tests/ConnectionFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace React\Tests\Socket;

use React\Socket\Connection;
use React\Socket\ConnectionFactory;
use React\Socket\ConnectionInterface;
use React\Socket\Server;
use React\EventLoop\StreamSelectLoop;
use React\Tests\Socket\Stub\ConnectionStub;

class ConnectionFactoryTest extends TestCase
{

/**
* @covers React\Socket\ConnectionFactory::createConnection
*/
public function testCreateConnection()
{
$loop = new StreamSelectLoop();

$factory = new ConnectionFactory([
'MEMORY' => ConnectionStub::class,
'STDIO' => Connection::class,
]);

$this->assertInstanceOf(ConnectionInterface::class, $factory->createConnection(fopen('php://memory', 'w+'), $loop));
$this->assertInstanceOf(ConnectionStub::class, $factory->createConnection(fopen('php://memory', 'w+'), $loop));

$this->assertInstanceOf(ConnectionInterface::class, $factory->createConnection(fopen(__FILE__, 'r'), $loop));

$this->assertInstanceOf(Connection::class, $factory->createConnection(fopen(__FILE__, 'r'), $loop));

}
}