2017-07-30 86 views
1

如何在使用ZF3和Doctrine时在数据库中实现会话?zf3与Doctrine的自定义会话SaveHandler

手册说:

There may be cases where you want to create a save handler where a save handler currently does not exist. Creating a custom save handler is much like creating a custom PHP save handler. All save handlers must implement Zend\Session\SaveHandler\SaveHandlerInterface. Generally if your save handler has options you will create another options class for configuration of the save handler.

我试图创建实现此接口的自定义类,但我得到了以下错误:

expects a class implementing Zend\Session\Storage\StorageInterface'

与此配置:

'session_storage' => [ 
//  'type' => SessionArrayStorage::class (with array storage works ok) 
     'type' => \Application\Session\SaveHandler\Doctrine::class (tried to implement suggested interface) 
    ], 

请注意,手册建议SaveHandlerInterface,但预计StorageInterface

任何示例如何做到这一点?

编辑:

我目前的执行情况。

global.php

'session_config' => [ 
     // Session cookie will expire in 1 hour. 
     'cookie_lifetime' => 60*60*1, 
     // Session data will be stored on server maximum for 30 days. 
     'gc_maxlifetime'  => 60*60*24*30, 
    ], 
    // Session manager configuration. 
    'session_manager' => [ 
     // Session validators (used for security). 
     'validators' => [ 
      RemoteAddr::class, 
      HttpUserAgent::class, 
     ] 
    ], 
    // Session storage configuration. 
    'session_storage' => [ 
     'type' => \Application\Session\Storage\Doctrine::class, 
    ], 
    'session_containers' => [ 
     'UserSession' 
    ] 

Module.php

/** 
    * This method is called once the MVC bootstrapping is complete. 
    */ 
    public function onBootstrap(MvcEvent $event) 
    { 
     $application = $event->getApplication(); 
     $serviceManager = $application->getServiceManager(); 



     // The following line instantiates the SessionManager and automatically 
     // makes the SessionManager the 'default' one 
     /** @var SessionManager $sessionManager */ 
     $sessionManager = $serviceManager->get(SessionManager::class); 

     $entityManager = $serviceManager->get('doctrine.entitymanager.orm_default'); 

     /** @var Doctrine $storage */ 
     $storage = $sessionManager->getStorage(); 
     $storage->setEntityManager($   
    } 

Application\Session\Storage\Doctrine.php

class Doctrine implements 
    IteratorAggregate, 
    StorageInterface, 
    StorageInitializationInterface 
{ 
    public function setEntityManager($em) { 
     $this->entityManager = $em; 
    } 

    // ... 
    // other functions as required by interfaces 
} 

这个方法奏效,但缺点是教义存储只会在可这个模块,我专门在每个re上注入它任务(Boostrap),而不是当它真的需要时(工厂)。

**更新:**

我写SaveHandler,不过貌似看重的请求都不会保留。

下面的代码:

class Doctrine extends ArrayStorage implements SaveHandlerInterface { 

    /** 
    * @param string $session_id 
    * @return string Encdoded session data string 
    */ 
    public function read($session_id) 
    { 
     $entity = $this->getEntity($session_id); 
     if ($entity) { 
      return $entity->getSessionData(); 
//   sample output: 
//   string '__ZF|a:2:{s:20:"_REQUEST_ACCESS_TIME";d:1501933765.497678;s:6:"_VALID";a:3:{s:25:"Zend\Session\Validator\Id";s:26:"3kr15rhi6ijhneu7rruro9gr76";s:33:"Zend\Session\Validator\RemoteAddr";s:9:"127.0.0.1";s:36:"Zend\Session\Validator\HttpUserAgent";s:133:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/57.0.2987.98 Chrome/57.0.2987.98 Safari/537.36";}}FlashMessenger|C:23:"Zend\Stdlib\ArrayObject":205:{a:4:{s:7:"storage";a:0:{}s:4:"flag";i:2;s:13:"iteratorClass";s:13:"ArrayI'... (length=645) 
//   note that counter is not present    
     } 
    } 

    /** 
    * @param string $session_id 
    * @param string $session_data Encoded session data string 
    * @return bool 
    */ 
    public function write($session_id, $session_data) 
    { 
//  sample input ($session_data): 
//  string '__ZF|a:2:{s:20:"_REQUEST_ACCESS_TIME";d:1501934933.9573331;s:6:"_VALID";a:3:{s:25:"Zend\Session\Validator\Id";s:26:"3kr15rhi6ijhneu7rruro9gr76";s:33:"Zend\Session\Validator\RemoteAddr";s:9:"127.0.0.1";s:36:"Zend\Session\Validator\HttpUserAgent";s:133:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/57.0.2987.98 Chrome/57.0.2987.98 Safari/537.36";}}UserSession|C:23:"Zend\Stdlib\ArrayObject":223:{a:4:{s:7:"storage";a:1:{s:7:"counter";i:1;}s:4:"flag";i:2;s:13:"iteratorCla'... (length=918) 
//  (note that counter variable is set) 

     $entity = $this->getEntity($session_id); 

     $entity->setSessionData($session_data); 
     $entity->setLifetime($this->getLifeTime()); 

     $this->getEntityManager()->persist($entity); 
     $this->getEntityManager()->flush($entity); 

     return true; 
    } 

    /** 
    * @param string $session_id 
    * @return Entity|null 
    */ 
    public function getEntity($session_id) 
    { 
     $this->entity = $this->getRepository()->find($session_id); 

     if (!$this->entity) { 
      $this->entity = new $this->entityName; 
      $this->entity->setId($session_id); 
     } 

     return $this->entity; 
    } 

    // .... 

} 
+0

当你到底得到这个错误? – akond

+0

我认为会话服务在会话管理器中注册时。 – takeshin

+0

我写了一个非常简单的POC片断,其中包含了一些简单的SaveHandlerInterface实现。到目前为止没有错误。 – akond

回答

0

说实话,我并没有使用与保存处理功能来管理会话学说。但是让我告诉你应该如何构造Zend\Session的每个部分,特别是对于SessionManager::class

SessionArrayStorage::class implements Zend\Session\Storage\StorageInterface并且用于存储会话数据以支持SessionManager::class

实际上这部分Zend\Session做了一件好事。它可以代替$_SESSION超全球,并使用的ArrayObject::class。它会给你一个很大的灵活性,这意味着你将能够使用这些功能:属性访问,元数据存储,锁定和不变性。 (老实说,我没有使用它们全部)。

'session_storage' => [ 
    // 'type' => SessionArrayStorage::class (with array storage works ok) 
    'type' => \Application\Session\SaveHandler\Doctrine::class (tried to implement suggested interface) 
], 

现在重点是您正在使用自定义保存处理程序,而不是存储这是不正确的事情。因为保存处理程序不执行Zend\Session\Storage\StorageInterface。这就是你得到这种类型错误的原因。

保存处理程序通常用于将会话数据存储在数据库,文件或高速缓存系统中。正如您正在制作自定义保存处理程序,这意味着您正在实施Zend\Session\SaveHandler\SaveHandlerInterface。因此,您必须使用open($savePath, $name),read($id),write($id, $data),destroy($id)等。

因此,要完全配置SessionManager::class来管理会话,您需要提供三件事:会话配置,会话存储和保存处理程序。例如

$sessionManager = new SessionManager(
    $sessionConfig, 
    $sessionStorage, 
    // provide your save handler here 
    $sessionSaveHandler 
); 

// this keeps this configuration in mind in later calls of 'Container::class' 
Container::setDefaultManager($sessionManager); 
return $sessionManager; 

现在我们配置了SessionManager::class。我们可以在需要时调用它。例如,在用户的登录凭证进行了验证之后。

$session = $e->getApplication() 
    ->getServiceManager() 
    ->get(SessionManager::class); 
$session->start(); 

此之后,我们将能够使用Zend\Session组件的Container::class部分为如下

// this would use the above configuration 
$container = new Container('initialized'); 

$session->getSaveHandler()->open('path', 'session-name'); 

// Write data to db, files etc 
// "$contents" must be serialized data; coentents can be: id, email etc 
$session->getSaveHandler()->write($session->getId(), $contents); 

// Read data 
$storedData = $session->getSaveHandler()->read($session->getId()); 

现在,我们将能够使用任何自定义属性和值存储像

$container->storedData = $storedData; 
$container->remoteAddr = 127.0.0.1; 

下一步,我们需要检索这些值,我们可以得到它们

$container = new Container('initialized'); 
print_r($container->storedData); 

//or 
echo $container->remoteAddr; 

希望这会帮助你一点!

+0

谢谢,但问题是如何将EntityManager依赖关系注入到自定义会话存储类(您可能会在[global.php config]中更改'type'的名称)以使其可在所有应用程序模块中访问。现在我在Module.php中执行此操作,检索存储实例并传递实体管理器。但是这样我注入完整的对象,而不是工厂,并且只能在当前模块中访问此对象。如何在会话管理器中注入EntityManager工厂? – takeshin

+0

那么,首先你需要为你的'Doctrine :: class'和'EntityManager :: class'创建工厂。然后在创建工厂时将'EntityManager :: class'注入'Doctrine :: class'的setter方法中。然后使用服务经理在需要的地方打电话给他们。谢谢 – unclexo

1

实际上,您需要实现这些接口,因为PHP需要SaveHandlerInterface,ZF3需要StorageInterface。你的存储处理器是它们之间的一个网关。

这应该工作。你可以注入工厂内的所有依赖关系。

应用/ src目录/ DoctrineSaveHandler.php

namespace Application; 

use Zend\Session\SaveHandler\SaveHandlerInterface; 
use Zend\Session\Storage\ArrayStorage; 

class DoctrineSaveHandler extends ArrayStorage implements SaveHandlerInterface 
{ 
    public function close() {} 
    public function destroy ($session_id) {} 
    public function gc ($maxlifetime) {} 
    public function open ($save_path, $name) {} 
    public function read ($session_id) {} 
    public function write ($session_id, $session_data) {} 
} 

配置/自动加载/ global.php

"service_manager" => [ 
    'aliases' => [ 
     \Zend\Session\SaveHandler\SaveHandlerInterface::class => \Zend\Session\Storage\StorageInterface::class 
    ], 
    'factories' => [ 
     \Zend\Session\Storage\StorageInterface::class => function() { 
      // ------------------------------- 
      // YOU NEED A PROPER FACTORY HERE! 
      // ------------------------------- 
      return new DoctrineSaveHandler(); 
     }, 
    ] 
] 
+0

这允许我注入'EntityManager',但是一旦我实现了'read()'和'write()'方法,编码后的值就会保存到数据库中,但处理不当(会话容器根本没有值)。数据分散/反序列化可能存在一些问题。我用当前的代码更新了这个问题。 – takeshin