1

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 ?

S S
  • 1,443
  • 2
  • 12
  • 42
  • Thanks @WillB. I tried ScoreHandler as a service method. – S S Jun 04 '21 at 13:14
  • For your second recommendation should I only install `"symfony/proxy-manager-bridge"` ? Or do it need anything else as well. – S S Jun 04 '21 at 13:16
  • Thanks for the informatin. I will definately go through your recommendation. But right now I am stuck with displaying score to only to user with given token. – S S Jun 04 '21 at 13:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/233328/discussion-between-s-s-and-will-b). – S S Jun 04 '21 at 13:43

1 Answers1

2

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.

Will B.
  • 17,883
  • 4
  • 67
  • 69
  • Thanks for your answer. It really helped understand. I have a question. Here nodejs is frontend and symfony will be backend ? As in my project I have Vue connected with symfony – S S Jun 06 '21 at 10:19
  • 1
    This NodeJS implementation operates on the backend as a server, listening for requests from the client-side. It would be the direct replacement for the Symfony Command, facilitating a bridge between WebSocket and HTTP, brokering the WebSocket request and converting it to an HTTP request to the Symfony Controller instead. Frontend is generally used to describe UI based and other design related elements. eg: Bootstrap. – Will B. Jun 06 '21 at 16:42
  • B how did you handled frontend ? I mean how did you passed data from symfony html to your node js? Previously, I was passing on btc click event handling `const message = { name: document.getElementById("name").value, message: document.getElementById("message").value }; socket.send(JSON.stringify(message)); addMessage(message.name, message.message);` – S S Jun 16 '21 at 15:08
  • Sorry I am bit confused by applying nodejs – S S Jun 16 '21 at 15:09
  • The same WS client on the frontend code should work, depends on your `socket.connect()` address and path. You will need to refactor the message parsing in `ws-broker.js` to match the desired info you are passing to Symfony in `$request->request...` I did not have your frontend code, so my example instead of `socket.send({ "name": "", "message": "" })`, the frontend code used `socket.send({ "data": { /*. .. */ }, "token": "" })` in order to segregate the *data* to pass as a request to Symfony and the token route. – Will B. Jun 16 '21 at 15:59
  • @SS if you make a separate question with your frontend code and current `ws-broker.js` a synopsis of where you're stumped and link me to it, I can help with it, since this is a separate question regarding the nodejs listener communicating with the WS client frontend. – Will B. Jun 16 '21 at 16:01
  • https://stackoverflow.com/questions/68012860/how-to-pass-message-to-websocket I have new question related here. – S S Jun 17 '21 at 04:17