Search code examples
phpwebsocketsymfony4

How to arguments into the Websocket handler?


I am developing a scorecard application where certain group of members are playing and can update their score in chart which needs to be reflected in team members screen too.

For this purpose I am using cboden/ratchet.

Each team have a common team code which I will pass using URL localhost:8000/{token} which will be passed from controller to twig.

I have following in command:

    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new ScoreHandler()
            )
        ),
        8080
    );
    $server->run();

ScoreHandler

public function __construct(EntityManagerInterface $entityManager)
{
    $this->connections = new SplObjectStorage;
    $this->entityManager = $entityManager;
}

This returns me Too few arguments to function App\Websocket\ScoreHandler::__construct(), 0 passed error.

I am not sure how to fix this error here. As I am planning to insert into the db and fetch records based on token and return to certain user group.

Can anybody help me ?


Solution

  • Word of Caution

    It is strongly discouraged from using PHP with Symfony and/or Doctrine for any long-running background processes (daemon), that listens for WebSocket (or other) connections, using Ratchet/ReactPHP style features in any production/real-world environments. PHP was not designed to run as a daemon. As such, without proper planning, the process will crash with either a Doctrine Connection exception with the MySQL Server Has Gone Away error or from memory leaks caused by maintaining the Entity Manager, Symfony service definitions and logger overflows.

    Using PHP as a daemon would require implementing unintuitive workarounds, such as a Messenger Queue (causes long delays between responses) or Lazy Proxy objects (causes code-level maintainability issues) and additional background processes, like supervisor and/or cron jobs to circumvent the inherent issues and recover from crashes.


    Provided you are using the default autowire configuration for your config/services.yaml and ScoreHandler is not in one of the excluded paths, the following options are feasible using dependency injection.

    # config/services.yaml
    services:
        # default configuration for services in *this* file
        _defaults:
            autowire: true      # Automatically injects dependencies in your services.
            autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    
        # makes classes in src/ available to be used as services
        # this creates a service per class whose id is the fully-qualified class name
        App\:
            resource: '../src/*'
            exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
    
        # ...
    

    Inject ScoreHandler as a Service

    The recommended approach is to inject the ScoreHandler service into the command, which will also automatically inject the EntityManagerInterface into the ScoreHandler.

    class YourCommand extends Command
    {
    
        private $handler;
    
        public function __construct(ScoreHandler $handler)
        {
            $this->handler = $handler;
            parent::__construct();
        }
    
        //...
    
        public function execute(InputInterface $input, OutputInterface $outpu)
        {
    
            //...
    
            $server = IoServer::factory(
                new HttpServer(
                    new WsServer($this->handler)
                ),
                8080
            );
            $server->run();
        }
    }
    

    Inject EntityManagerInterface and pass to ScoreHandler

    Since you are creating a new instance of ScoreHandler manually, it requires the EntityManagerInterface to be supplied as an argument to the constructor.

    This is not recommended, as a service already exists that is already autowired as in the previous example.

    class YourCommand extends Command
    {
    
        private $em;
    
        public function __construct(EntityManagerInterface $em)
        {
            $this->em = $em;
            parent::__construct();
        }
    
        //...
    
        public function execute(InputInterface $input, OutputInterface $outpu)
        {
    
            //...
    
            $server = IoServer::factory(
                new HttpServer(
                    new WsServer(
                        new ScoreHandler($this->em)
                    )
                ),
                8080
            );
            $server->run();
        }
    }
    

    NodeJS Alternative

    While PHP was not designed as a daemon, nodejs and several other platforms are able to facilitate listening for connections. They can be used to forward the request to your Symfony web application and send a response back to the client, as a request broker.

    See https://stackoverflow.com/a/68027150/1144627 for an example of a WebSocket Broker Service.