I am having trouble triggering events from a controller that inherits from use yii\rest\Controller
.
I have the run.php
file to run the server:
<?php
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use app\modules\apiv1\websocket\WebSocketServer;
require __DIR__ . '/../../../vendor/autoload.php';
require __DIR__ . '/../../../config/bootstrap.php';
$server = IoServer::factory(
new HttpServer(
new WsServer(
new WebSocketServer()
)
),
8081,
'0.0.0.0'
);
echo "WebSocket server running at ws://0.0.0.0:8081\n";
$server->run();
This is my WebSocketServer.php
file:
<?php
namespace app\modules\apiv1\websocket;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Yii;
use app\models\CrugeUser;
class WebSocketServer implements MessageComponentInterface
{
protected static $instance = null;
protected $clients = [];
protected $users = [];
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function onOpen(ConnectionInterface $conn)
{
// Uses resourceId as a unique identifier for the connection
$this->clients[$conn->resourceId] = $conn;
$queryParams = $this->parseQueryString($conn->httpRequest->getUri()->getQuery());
if (!isset($queryParams['token'])) {
$this->sendError($conn, 'Missing token');
return;
}
if (!isset($queryParams['device']) || empty($queryParams['device'])) {
$queryParams['device'] = 'unknown';
}
try {
$userInfo = $this->validateToken($queryParams['token']);
$this->users[$conn->resourceId] = [
'data' => $userInfo['data'],
'idconn' => $conn->resourceId,
'browser' => $queryParams['device'],
];
echo "User {$userInfo['id']} connected from {$queryParams['device']}.\n";
$this->broadcast([
'type' => 'user_list',
'users' => array_values($this->users),
]);
} catch (\Exception $e) {
echo "Authentication error: {$e->getMessage()}\n";
$this->sendError($conn, 'Authentication error: ' . $e->getMessage());
}
}
public function onMessage(ConnectionInterface $conn, $msg)
{
$data = json_decode($msg, true);
if (!is_array($data) || !isset($data['message'])) {
$conn->send(json_encode(['error' => 'Invalid message format']));
return;
}
$userId = $this->users[$conn->resourceId]['data']['iduser'] ?? null;
if (!$userId) {
$conn->send(json_encode(['error' => 'Unauthenticated user']));
return;
}
$this->broadcast([
'type' => 'chat_message',
'from' => [
'id' => $userId,
'resourceId' => $conn->resourceId,
],
'message' => $data['message'],
]);
}
public function onClose(ConnectionInterface $conn)
{
if (isset($this->clients[$conn->resourceId])) {
unset($this->clients[$conn->resourceId]);
}
if (isset($this->users[$conn->resourceId])) {
echo "User disconnected: ({$conn->resourceId})\n";
unset($this->users[$conn->resourceId]);
$this->broadcast([
'type' => 'user_list',
'users' => array_values($this->users),
]);
}
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "Error: {$e->getMessage()}\n";
}
protected function validateToken(string $token)
{
$jwt = Yii::$app->jwt;
try {
$parsedToken = $jwt->getParser()->parse($token);
if (!$jwt->validateToken($parsedToken)) {
throw new \Exception('Invalid or expired token');
}
$userId = $parsedToken->getClaim('uid');
$user = CrugeUser::findOne($userId);
if (!$user) {
throw new \Exception('User not found');
}
return [
'id' => $userId,
'data' => \app\modules\apiv1\helpers\MapData::mapData($user),
];
} catch (\Exception $e) {
throw new \Exception('Error validating token: ' . $e->getMessage());
}
}
protected function broadcast(array $message)
{
$encodedMessage = json_encode($message);
foreach ($this->clients as $client) {
$client->send($encodedMessage);
}
}
private function parseQueryString($queryString)
{
$result = [];
parse_str($queryString, $result);
return $result;
}
private function sendError(ConnectionInterface $conn, string $message)
{
$conn->send(json_encode(['error' => $message]));
$conn->close();
}
public function broadcastNewOrder($newOrder) {
echo "Order to send $newOrder";
$this->broadcast([
'type' => 'create_order',
'newOrder' => $newOrder,
]);
}
}
When I want to call an event from a controller, the $clients
parameter in the WebSocketServer.php
class is 0 because it is a different instance. How can I fix this issue?
use yii\rest\Controller;
class OrderController extends Controller
{
private function sendEventSocket($data)
{
$data = \app\modules\apiv1\helpers\MapData::mapData($model);
$webSocketServer = \app\modules\apiv1\websocket\WebSocketServer::getInstance();
$webSocketServer->broadcastNewOrder($data);
}
}