2013-03-14 104 views
3

我目前使用的是Symfony 2.1.8和內置的PdoSessionHandler如何將自定義字段添加到會話表

我想在會話表中添加一個user_id字段來標識會話屬於哪個(登錄)用戶。我的想法是,我可以強制用戶重新登錄銷燬會話。在我的情況下,如果用戶的權限被更新,將會發生。

我看了一下內置的PdoSessionHandler,你不能擴展,因爲那些愚蠢的私有變量。

所以我試着創建一個新的(複製/粘貼)並添加我的列user_id。 如果用戶未登錄(匿名用戶),此欄可以爲空。

所以我想寫這個user_id在處理程序的寫入方法。該用戶已存儲在$data中,所以我想我可以檢查該用戶是否存在,獲取它的id並將其添加到插入/更新查詢中。

問題是$data被編碼 - 我猜session_encode() - 所以我不能確定這是有史以來處理我的新領域的最好的地方,但同時我看不到其他地方我可以這樣做,因爲我需要更新這個MySQL查詢來插入新字段的值。

所以我的問題是:哪裏是最好的地方來處理這個額外的領域?以及如何設置此user_id值?

在另一個說明,somehing真的很煩人的是,Symfony正在創建一個新的cookie每次我登錄或註銷。因此,數據庫最終會有很多記錄(總是一樣的用戶)。爲什麼Symfony始終沒有使用相同的cookie值?

回答

3

我不確定修改會話是個好主意,您可以將會話ID存儲在用戶實體中,並在需要時刪除它們。這樣,您可以確保只有用戶每次只能登錄一個會話。

完成它的最簡單方法是使用登錄監聽器。

sessionId字段添加到用戶實體(或文檔或任何你使用的持久性):

// Acme/UserBundle/Entity/User.php 
namespace Acme\UserBundle\Entity; 

use Doctrine\ORM\Mapping as ORM; 

/** 
* ORM\Entity 
* @ORM\Table(name="fos_user") 
*/ 
class User { 
    // ... 

    /** 
    * @ORM\Column(name="session_id", type="string") 
    */ 
    private $sessionId; 

    public function getSessionId() { 
    return $this->sessionId; 
    } 

    public function setSessionId($sessionId = null) { 
    $this->sessionId = $sessionId; 
    return $this; 
    } 
} 

,並添加監聽器:

namespace Dbla\UserBundle\Listener; 

use Symfony\Component\HttpFoundation\Session; 
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; 

class LoginListener 
{ 
    protected $doctrine; 

    protected $session; 

    public function __construct(Session $session, Registry $doctrine) 
    { 
    $this->doctrine = $doctrine; 
    $this->session = $session; 
    } 

    public function onLogin(InteractiveLoginEvent $event) 
    { 
    $user = $event->getAuthenticationToken()->getUser(); 

    if ($user) { 
     $user->setSessionId($this->session->getId()); 
     $em = $this->doctrine->getEntityManager(); 
     $em->persist($user); 
     $em->flush(); 
    } 
    } 
} 

並將其添加爲一個服務:

services: 
    acme_user.listsner.login: 
    class: Acme\UserBundle\Listener\LoginListener 
    arguments: [ @session, @doctrine ] 
    tags: 
     - { name: kernel.event_listener, event: security.interactive_login, method: onLogin } 

然後您可以簡單地刪除用戶的會話:

$users = []; // ... get user list 
$sessionIds = array_map(function($user) { 
    return $user->getId(); 
}); 
if (count(sessionIds) > 0) { 
    $sql = 'DELETE FROM session WHERE session_id IN (' . implode($sessionIds, ',') . ')'; 
    $entityManager->getConnection()->exec($sql); 
} 
foreach ($users as $user) { 
    $user->setSessionId(null); 
    $entityManager->persist($user); 
} 
$entityManager->flush(); 
+1

這不是一個壞主意,但...在會話表中擁有'user_id'我只需要執行一個查詢:一個刪除。另外我寧願在會話表上執行查詢,這意味着比用戶表存儲更少的記錄(理論上),因爲我應該只有登錄用戶的會話。我不太喜歡的是每個我想銷燬一個會話,我需要做一個刪除和更新用戶。 – maxwell2022 2013-03-14 13:27:42

+0

從用戶中刪除'sessionId'也可以在一個查詢中完成。但是這樣你可以註冊監聽器來在用戶會話被刪除時執行額外的任務('用戶'實體被修改)。它僅被刷新一次,因此只有最小的性能影響。 – 2013-03-18 17:35:16

+0

取決於您的應用程序字符,但通常會有比認證的更多的匿名會話。匿名用戶也有會話。 – 2013-03-18 17:38:04

4

你可以擴展PdoSessionHandler(解決方案> = Symfony的2.1):

namespace Acme\DemoBundle\HttpFoundation\Session\Storage\Handler; 

use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; 
use Symfony\Component\Security\Core\SecurityContext; 

class UserIdPdoSessionHandler extends PdoSessionHandler 
{ 
    /** 
    * @var \PDO PDO instance. 
    */ 
    private $pdo; 

    /** 
    * @var array Database options. 
    */ 
    private $dbOptions; 

    /** 
    * @var SecurityContext 
    */ 
    private $context; 

    public function __construct(\PDO $pdo, array $dbOptions = array(), SecurityContext $context) 
    { 
     $this->pdo = $pdo; 
     $this->dbOptions = array_merge(
      array('db_user_id_col' => 'user_id'), 
      $dbOptions 
     ); 
     $this->context = $context; 

     parent::__construct($pdo, $dbOptions); 
    } 

    public function read($id) 
    { 
     // get table/columns 
     $dbTable = $this->dbOptions['db_table']; 
     $dbDataCol = $this->dbOptions['db_data_col']; 
     $dbIdCol = $this->dbOptions['db_id_col']; 

     try { 
      $sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id"; 

      $stmt = $this->pdo->prepare($sql); 
      $stmt->bindParam(':id', $id, \PDO::PARAM_STR); 

      $stmt->execute(); 
      // it is recommended to use fetchAll so that PDO can close the DB cursor 
      // we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777 
      $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM); 

      if (count($sessionRows) == 1) { 
       return base64_decode($sessionRows[0][0]); 
      } 

      // session does not exist, create it 
      $this->createNewSession($id); 

      return ''; 
     } catch (\PDOException $e) { 
      throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e); 
     } 
    } 

    /** 
    * {@inheritDoc} 
    */ 
    public function write($id, $data) 
    { 
     // get table/column 
     $dbTable  = $this->dbOptions['db_table']; 
     $dbDataCol = $this->dbOptions['db_data_col']; 
     $dbIdCol  = $this->dbOptions['db_id_col']; 
     $dbTimeCol = $this->dbOptions['db_time_col']; 
     $dbUserIdCol = $this->dbOptions['db_user_id_col']; 

     //session data can contain non binary safe characters so we need to encode it 
     $encoded = base64_encode($data); 

     $userId = $this->context->isGranted('IS_AUTHENTICATED_REMEMBERED') ? 
      $this->context->getToken()->getUser()->getId() : 
      null 
     ; 

     try { 
      $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); 

      if ('mysql' === $driver) { 
       // MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged 
       // it could result in calling createNewSession() whereas the session already exists in 
       // the DB which would fail as the id is unique 
       $stmt = $this->pdo->prepare(
        "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id) " . 
        "ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = VALUES($dbTimeCol)" 
       ); 
       $stmt->bindParam(':id', $id, \PDO::PARAM_STR); 
       $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); 
       $stmt->bindValue(':time', time(), \PDO::PARAM_INT); 
       $stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR); 
       $stmt->execute(); 
      } elseif ('oci' === $driver) { 
       $stmt = $this->pdo->prepare("MERGE INTO $dbTable USING DUAL ON($dbIdCol = :id) ". 
         "WHEN NOT MATCHED THEN INSERT ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, sysdate, :user_id) " . 
         "WHEN MATCHED THEN UPDATE SET $dbDataCol = :data WHERE $dbIdCol = :id"); 

       $stmt->bindParam(':id', $id, \PDO::PARAM_STR); 
       $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); 
       $stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR); 
       $stmt->execute(); 
      } else { 
       $stmt = $this->pdo->prepare("UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id"); 
       $stmt->bindParam(':id', $id, \PDO::PARAM_STR); 
       $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); 
       $stmt->bindValue(':time', time(), \PDO::PARAM_INT); 
       $stmt->execute(); 

       if (!$stmt->rowCount()) { 
        // No session exists in the database to update. This happens when we have called 
        // session_regenerate_id() 
        $this->createNewSession($id, $data); 
       } 
      } 
     } catch (\PDOException $e) { 
       throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e); 
     } 

     return true; 
    } 

    private function createNewSession($id, $data = '') 
    { 
     // get table/column 
     $dbTable  = $this->dbOptions['db_table']; 
     $dbDataCol = $this->dbOptions['db_data_col']; 
     $dbIdCol  = $this->dbOptions['db_id_col']; 
     $dbTimeCol = $this->dbOptions['db_time_col']; 
     $dbUserIdCol = $this->dbOptions['db_user_id_col']; 

     $userId = $this->context->isGranted('IS_AUTHENTICATED_REMEMBERED') ? 
      $this->context->getToken()->getUser()->getId() : 
      null 
     ; 

     $sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol, $dbUserIdCol) VALUES (:id, :data, :time, :user_id)"; 

     //session data can contain non binary safe characters so we need to encode it 
     $encoded = base64_encode($data); 
     $stmt = $this->pdo->prepare($sql); 
     $stmt->bindParam(':id', $id, \PDO::PARAM_STR); 
     $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); 
     $stmt->bindValue(':time', time(), \PDO::PARAM_INT); 
     $stmt->bindParam(':user_id', $userId, \PDO::PARAM_STR); 
     $stmt->execute(); 

     return true; 
    } 
} 

而且配置會話使用它:

# config.yml 
framework: 
    session: 
     # ... 
     handler_id: session.storage.custom 

parameters: 
    pdo.db_options: 
     db_table:  session 
     db_id_col:  session_id 
     db_data_col: session_value 
     db_time_col: session_time 
     db_user_id_col: session_user_id 

services: 
    pdo: 
     class: PDO 
     arguments: 
      dsn:  "mysql:host=%database_host%;dbname=%database_name%" 
      user:  "%database_user%" 
      password: "%database_password%" 

    session.storage.custom: 
     class: Acme\DemoBundle\HttpFoundation\Session\Storage\Handler\UserIdPdoSessionHandler 
     arguments: [ @pdo, "%pdo.db_options%", @security.context ] 
+0

你的代碼似乎有錯誤。構造函數應該以'$ this-> dbOptions'開始,而不僅僅是'dbOptions'。 – Maerlyn 2013-07-17 13:26:31

+0

謝謝,修復。 – 2013-07-29 19:39:19

+0

代碼中的其他錯誤:'$ securityContext'未定義:( – 2013-11-21 13:37:43

相關問題