WebSockets with Symfony2
I've started playing around with WebSockets in PHP, integrating them into my pet Symfony2 project.
The first library I grabbed was known as php-websocket. It was a bit of a mess. The interface for applications into the server was stuffy, and I ended up having to go to great lengths to integrate it into the services container. This integration work was done in a bundle, VarspoolWebsocketBundle. I later greatly cleaned up the upstream, renaming php-websocket to Wrench.
I've just got done reimplementing all my WebSocket work in Ratchet. And I'm pleased to say that I now consider Wrench and VarspoolWebsocketBundle completely deprecated; I definitely wouldn't consider using them for new projects, and I'm considering posting a warning in the README.
Ratchet needs very little effort to use with Symfony. I'm using a Redis session handler, and Ratchet provides a light integration there. It also works well with the Command component. Here's my command:
class ListenCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('websocket:listen')
->setDescription('Listen for websocket requests (blocks indefinitely)')
->addOption('port', 'p', InputOption::VALUE_REQUIRED, 'The port to listen on', 8000)
->addOption('interface', 'i', InputOption::VALUE_REQUIRED, 'The interface to listen on', '0.0.0.0');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$application = new AggregateApplication();
$server = new WampServer($application);
// Wrap server in a session provider
$handler = $this->getContainer()->get('session.handler');
if ($handler instanceof \SessionHandlerInterface) {
$server = new SessionProvider($server, $handler);
}
$server = new WsServer($server);
$server = IoServer::factory(
$server,
$input->getOption('port'),
$input->getOption('interface')
);
$server->run();
}
}
Short, simple, and easy to extend with further dependencies, options and arguments.
Ratchet still has a few limitations (notably, it's not easy to run multiple applications with a single server). But I can get around that, and they're looking at integrating a Symfony2 Routing to help.
In the meantime, I use a magic AggregateApplication
class that farms out events to multiple other applications with __call()
and call_user_func()
:
class AggregateApplication implements WampServerInterface
{
protected $children;
public function __construct()
{
$this->children = array(
new ChatApplication(),
new IndicatorApplication()
);
}
public function __call($name, array $arguments)
{
foreach ($this->children as $child) {
call_user_func_array(array($child, $name), $arguments);
}
}
/**
* @see \Ratchet\Wamp\WampServerInterface::onSubscribe()
*/
public function onSubscribe(ConnectionInterface $connection, $topic)
{
return $this->__call(__FUNCTION__, func_get_args());
}
// ... other callbacks
}
This AggregateApplication is instantiated in the Command class, which is ContainerAware. So, you can see how easy it would be to collect tagged application classes out of the service container, or inject sets of dependencies.
As for performance, I strongly recommend you just use a WAMP interface to your application code. This will allow you to take advantage of middleware, and have something other than PHP serve your WebSockets in production. Ratchet makes this easy.