如何在Ratchet websocket应用程序中使用遗留会话($ _SESSION)和symfony会话

时间:2018-02-17 15:59:43

标签: php symfony session websocket

好吧,让我先说我不是框架的忠实粉丝。我已经使用遗留会话(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'];

现在这完全正常,用户可以登录并访问他们的个人资料,但现在我正在尝试实现websockets。所有教程只是向您展示如何实现它没有会话,但我想实现一个通知系统,当一个用户做某事时(例如添加另一家公司发布的项目),我希望要通知的特定公司。这是我在实现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()}";
}

当我尝试访问会话变量时,会话为空。我在使用PhpBridgeSessionStorage https://symfony.com/doc/current/session/php_bridge.html时遵循了有关symfony的文档,但这一点并不清楚。这是我在Notification类(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){

    }
}

当我向终端输出$conn->Session->all()时,我会收到一个警告PHP Warning: SessionHandler::open(): Session is not active以及一个空数组,当我输出$_SESSION到终端时,我得到:

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

我没有获得会话数据,我尝试使用其他会话处理程序,如MemcacheSessionHandler, MemcachedSessionHandler, PdoSessionHandler但是他们都将会话留空,我尝试了其他存储类,但symfony docs建议使用PhpBridgeSessionStorage时您希望将旧会话与symfony会话集成。

我知道有推送通知的框架,但我不想使用他们的第三方服务。

我已经研究了好几天,但找不到任何有用的东西。

1 个答案:

答案 0 :(得分:0)

如果你不是像我这样的框架的忠实粉丝,那么我猜这个答案可以帮助你。我实际上找不到任何关于如何在WebSocket上使用Symfony会话和遗留会话($ _SESSION)的好文档。大多数教程和线程建议只在整个应用程序中使用Symfony会话......但是如果你喜欢我并且已经在大多数应用程序中使用了遗留会话,那么你可以尝试这种替代方案。

摆脱你的symfony会话代码和棘轮会话提供程序,这就是我的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\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()}";
}

您需要做的下一件事是当客户端连接到WebSocket服务器时,您需要获取他们的PHPSESSID。在我的案例Notification类中,您可以从app类中的连接对象获取。执行$conn->httpRequest->getHeader('Cookie')会返回一系列cookie,因此您需要编写代码来循环并获取PHPSESSID,如下所示:

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

一旦你拥有它,你可以通过设置会话ID session_id($sessid)来使用它来设置websocket服务器上的会话但是为了避免我遇到的问题,我建议你在用户一旦将PHPSESSID存储在数据库中登录...此ID应该是唯一的,以防止冲突不使用session_regenerate_id(),使用session_create_id()。因此,在WebSocket服务器上,您将运行查询以获取您拥有PHPSESSID的用户的信息。我建议像我这样做创建Session类:

<?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");
    }
}

因此,当您在app类中创建会话对象时,如果您提供了“Cookie”数组,则您将拥有登录用户的信息:

<?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]);


    }
}

这是我可以为旧会话的Symfony会话提出的最佳选择。随意提问。