简体   繁体   中英

How can I use legacy sessions ($_SESSION) with symfony sessions in Ratchet websocket application

Ok let me start out by saying I'm not a big fan of frameworks. I have already developed my application using legacy sessions (login.php):

<?php
$_SESSION{'user_type'} = 'u';
$_SESSION{'id'} = $q[0]['id'];
$_SESSION{'email'} = $q[0]['email'];
$_SESSION{'username'} = $q[0]['username'];
$_SESSION{'firstname'} = $q[0]['firstname'];
$_SESSION{'lastname'} = $q[0]['lastname'];
$_SESSION{'about_me'} = $q[0]['about_me'];
$_SESSION{'gender'} = $q[0]['gender'];

Now this works perfectly fine, users can login and access their profiles but now I'm trying to implement websockets. All the tutorials just show you how to implement it without sessions, but I want to implement a notification sytem where when one user does something (for example add an item that another company posted), , I want that specific company to be notified. This is my code that runs on the terminal when implementing websockets (server.php):

<?php
require 'vendor/autoload.php';
require 'include/connect.php';
include 'models/Notify.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\Session\SessionProvider;
//use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
use Ratchet\App;
use Notify\Notification;


try{
    $domain = '127.0.0.1';

    $storage = new PhpBridgeSessionStorage();
    $session = new Session($storage);
    $session->migrate();

    $server = IoServer::factory(
        new HttpServer(
            new SessionProvider(
                new WsServer(
                    new Notification
                ),
                new \SessionHandler
            )
        ),
        3002,
        $domain
    );

    $server->run();

}catch(Exception $e){
    echo "Error: {$e->getMessage()}";
}

When I try to access the session variables the session is empty. I followed the documentation on symfony when using PhpBridgeSessionStorage https://symfony.com/doc/current/session/php_bridge.html but this was not clear. This is the code I use in my Notification class (Notify.php):

<?php
namespace Notify;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;

class Notification implements MessageComponentInterface{
    protected $clients;
    private $users;

    function __construct(){
        $this->clients = new \SplObjectStorage;
        $this->users = [];
    }

    public function onOpen(ConnectionInterface $conn){

        echo "Connected ({$conn->resourceId})\n";
        //$conn->Session->start();
        var_dump($conn->Session->all());
    }

    public function onMessage(ConnectionInterface $from, $msg){
        //echo "$msg [{$from->Session->get('username')}]\n";
    }

    public function onClose(ConnectionInterface $conn){
        echo "\nConnection Closed ({$conn->resourceId})\n";
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e){

    }
}

When I output $conn->Session->all() to the terminal I get a warning PHP Warning: SessionHandler::open(): Session is not active along with an empty array and when I output $_SESSION to the terminal I get:

 array(3) {
  ["_sf2_attributes"]=>
  &array(0) {
  }
  ["_symfony_flashes"]=>
  &array(0) {
  }
  ["_sf2_meta"]=>
  &array(3) {
    ["u"]=>
    int(1518882401)
    ["c"]=>
    int(1518882401)
    ["l"]=>
    string(1) "0"
  }
}

I am getting no session data, I've tried using other session handlers Like MemcacheSessionHandler, MemcachedSessionHandler, PdoSessionHandler but they all leave the session empty, I've tried other storage classes but symfony docs recommend using PhpBridgeSessionStorage when you want to integrate legacy sessions with symfony sessions.

I know that there are frameworks out there for push notifications but I don't want to use their third-party services.

I've been researching for days and I cannot find anything useful.

If your not a big fan of frameworks like myself, then I'm guessing this answer can help you out. I actually couldn't find any good documentation on how to use Symfony sessions with legacy sessions ($_SESSION) on a WebSocket. Most tutorials and threads recommend just using Symfony sessions soley throughout your application... But if your like me and have already used legacy sessions throughout most of your aplication then you can try this alternative.

Get rid of your symfony session code and ratchet session providers, This is what my server.php looks like now:

<?php
require 'vendor/autoload.php';
require 'include/connect.php';
include 'models/Notify.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\App;
use Notify\Notification;


try{
    $domain = '127.0.0.1';

    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new Notification
            )
        ),
        3002,
        $domain
    );

    $server->run();

}catch(Exception $e){
    echo "Error: {$e->getMessage()}";
}

The next thing you need to do is whenever a client connects to the WebSocket Server, you need to grab their PHPSESSID. You can get from the connection object in your app class in my case Notification class. Doing $conn->httpRequest->getHeader('Cookie') will return an array of cookies, therefore you'll need to write code to loop through and get the PHPSESSID, something like this:

function getPHPSESSID(array $cookies){
    foreach($cookies as $cookie):
        if(strpos($cookie, "PHPSESSID") == 0):
            $sess_id = explode('=', $cookie)[1];
            break;
        endif;
    endforeach;
    return $sess_id;
}

Once you have it you can use it to set the session on the websocket server by setting session id session_id($sessid) but to avoid the problems I faced doing that, I suggest you store the PHPSESSID in the database once the user logs in... This id should be unique, to prevent collisions don't use session_regenerate_id() , use session_create_id() . So on the WebSocket Server you'll run a query to grab the info of the user you have the PHPSESSID for. I recommend creating Session class like I did to do this:

<?php
namespace Library;
class _Session {
    private $sess_id;
    private $session;

    public function __construct(array $cookies){
        include 'include/connect.php';
        foreach($cookies as $cookie):
            if(strpos($cookie, "PHPSESSID") == 0):
                $this->sess_id = explode('=', $cookie)[1];
                break;
            endif;
        endforeach;
        $query = "SELECT * FROM general_user WHERE sess_id = ?";

        $query_run = $database->prepare($query);

        if($query_run->execute([$this->sess_id])):
            if($query_run->rowCount() == 1):
                $query = "SELECT g.id, g.email, u.username, u.firstname, u.lastname, g.about_me, u.gender, p.name AS profile_pic, 'u' AS user_type FROM general_user g INNER JOIN userdata u ON u.id = g.id LEFT JOIN profile_photo p ON g.id = p.user_id WHERE sess_id = ?";

                $query_run = $database->prepare($query);

                if($query_run->execute([$this->sess_id])):
                    if($query_run->rowCount() == 0):
                        $query = "SELECT g.id, g.email, g.about_me AS company_description, c.category, c.subcategory, c.company_name, c.contact_num, c.website, p.name AS profile_pic, 'c' AS user_type FROM general_user g INNER JOIN companydata c ON c.id = g.id LEFT JOIN profile_photo p ON g.id = p.user_id WHERE sess_id = ?";

                        $query_run = $database->prepare($query);

                        if($query_run->execute([$this->sess_id])):
                            $this->session = $query_run->fetchAll()[0];
                        endif;
                    else:
                        $this->session = $query_run->fetchAll()[0];
                    endif;
                endif;
            else:
                $this->session = [];
            endif;
        endif;

    }

    function get($key){
        foreach($this->session as $k => $value)
            if($key == $k)
                return $value;
        throw new \Exception("Undefined index $key");
    }
}

Hence when you create a session object in your app class you'll have info on the user logged in, given you've provided the 'Cookie' array:

<?php
namespace Notify;
require __DIR__.'/../vendor/autoload.php';
include __DIR__.'/../classes/CustomSession.php';

use Library\_Session;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;

class Notification implements MessageComponentInterface{
    protected $clients;

    function __construct(){
        $this->clients = [];
        echo "WebSocket Server Running...\n";
    }

    public function onOpen(ConnectionInterface $conn){
        $session = new _Session($conn->httpRequest->getHeader('Cookie'));//My custom session

        if($session->get('user_type') == 'u'){
            echo $session->get('username')." has connected\n";
        }else{
            echo $session->get('company_name')." has connected\n";
        }

        array_push($this->clients, ['connection' => $conn, 'session'=> $session]);


    }
}

This was the best alternative I could come up with for Symfony sessions with legacy sessions. Feel free to ask questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM