我最終通過擴展SessionManager,SessionStorage和SessionSaveHandler類並重寫了一些功能來解決這個問題。我也改變了Module.php和module.config.php文件。這就是更改後的樣子:
module.config.php
<?php
/* ...required use statements... */
return array(
'session' => array(
'config' => array(
'class' => 'Zend\Session\Config\SessionConfig',
'options' => array(
'name' => [my session name],
),
),
'storage' => 'MySession\Model\MySessionStorage',
'save_handler' => 'MySession\Model\MySessionSaveHandler'
),
'service_manager' => array(
'factories' => array(
'session_service' => function($serviceManager) {
$entityManager = $serviceManager->get('Doctrine\ORM\EntityManager');
return new SessionService($entityManager, 'MySession');
},
'MySession\Model\MySessionSaveHandler' => function($serviceManager) {
$sess = $serviceManager->get('onmysession_service');
/* @var $adapter \Zend\Db\Adapter\Adapter */
$adapter = $sm->get('Zend\Db\Adapter\Adapter');
$tableGateway = new TableGateway('mytablename', $adapter);
return new MySessionSaveHandler($tableGateway, new DbTableGatewayOptions(), $sess);
},
'MySessionManager' => function ($sm) {
$config = $sm->get('config');
if (isset($config['session'])) {
$session = $config['session'];
$sessionConfig = null;
if (isset($session['config'])) {
$class = isset($session['config']['class']) ? $session['config']['class'] : 'Zend\Session\Config\SessionConfig';
$options = isset($session['config']['options']) ? $session['config']['options'] : array();
$sessionConfig = new $class();
$sessionConfig->setOptions($options);
}
$sessionStorage = null;
if (isset($session['storage'])) {
$class = $session['storage'];
$sessionStorage = new $class();
}
$sessionSaveHandler = null;
if (isset($session['save_handler'])) {
// class should be fetched from service manager since it will require constructor arguments
$sessionSaveHandler = $sm->get($session['save_handler']);
}
$sessionManager = new MySessionManager($sessionConfig, $sessionStorage, $sessionSaveHandler);
} else {
$sessionManager = new MySessionManager();
}
MySession::setDefaultManager($sessionManager);
return $sessionManager;
},
),
),
'db' => array(
[db info here]
),
/***************************************************************************************************************
* Below is the doctrine configuration which holds information about the entities in this module and some
* other doctrine stuff like orm drivers etc.
***************************************************************************************************************/
'doctrine' => array(
'driver' => array(
'session_entities' => array(
'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/MySession/Entity')
),
'orm_default' => array(
'drivers' => array(
'MySession\Entity' => 'session_entities'
),
),
),
),
);
Module.php
<?php
namespace MySession;
/* ...required use statements... */
/***************************************************************************************************
* This class holds a few utility functions related to loading the module and accessing config
* files for the module etc. These functions are primarily used by Zend under the hood.
***************************************************************************************************/
class Module implements AutoloaderProviderInterface, ConfigProviderInterface
{
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
// create the session manager
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$sessionManager = $e->getApplication()
->getServiceManager()
->get('MySessionManager');
$sessionManager ->start();
// attach dispatch listener to validate user session
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($sessionManager, 'handleSessionValidation')); // TODO: we already handleSessionValidation on bootstrap, find out if it's necessary to do it on dispatch as well
}
/***************************************************************************************************
* Returns the location of the module.config.php file. This function is used by the Zend Framework
* underneath the hood.
***************************************************************************************************/
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
/***************************************************************************************************
* Returns the Zend StandardAutoLoader which contains the directory structure of the module source
* folder.
***************************************************************************************************/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
MySessionManager
<?php
namespace MySession\Model;
/* ...required use statements... */
class MySessionManager extends SessionManager
{
/**
* Is this session valid?
*
* A simple validation: checks if a row for the session name exists in the database
*
* @return bool
*/
public function isValid()
{
$id = $_COOKIE[SessionVariableNames::$SESSION_NAME];
return !is_null($this->getSaveHandler()->readMetadata($id));
}
/**
* checks if the session is valid and dies if not.
*/
public function handleSessionValidation() {
if(stristr($_SERVER["SCRIPT_NAME"],"login.php"))
{
// we don't need to check the session at the login page
return;
}
if (!$this->isValid()) {
die("Not logged in.")
}
}
/**
* Start session
*
* If no session currently exists, attempt to start it. Calls
* {@link isValid()} once session_start() is called, and raises an
* exception if validation fails.
*
* @param bool $preserveStorage If set to true, current session storage will not be overwritten by the
* contents of $_SESSION.
* @return void
* @throws RuntimeException
*/
public function start($preserveStorage = false)
{
if ($this->sessionExists()) {
return;
}
$saveHandler = $this->getSaveHandler();
if ($saveHandler instanceof SaveHandlerInterface) {
// register the session handler with ext/session
$this->registerSaveHandler($saveHandler);
}
// check if old session data exists and merge it with new data if so
$oldSessionData = [];
if (isset($_SESSION)) {
$oldSessionData = $_SESSION;
}
session_start();
if ($oldSessionData instanceof \Traversable
|| (! empty($oldSessionData) && is_array($oldSessionData))
) {
$_SESSION = ArrayUtils::merge($oldSessionData, $_SESSION, true); // this may not act like you'd expect, because the sessions are stored in ArrayObjects, so the second will always overwrite the first
}
$storage = $this->getStorage();
// Since session is starting, we need to potentially repopulate our
// session storage
if ($storage instanceof SessionStorage && $_SESSION !== $storage) {
if (!$preserveStorage) {
$storage->fromArray($_SESSION);
}
$_SESSION = $storage;
} elseif ($storage instanceof StorageInitializationInterface) {
$storage->init($_SESSION);
}
$this->handleSessionValidation();
}
/**
* Write session to save handler and close
*
* Once done, the Storage object will be marked as isImmutable.
*
* @return void
*/
public function writeClose()
{
// The assumption is that we're using PHP's ext/session.
// session_write_close() will actually overwrite $_SESSION with an
// empty array on completion -- which leads to a mismatch between what
// is in the storage object and $_SESSION. To get around this, we
// temporarily reset $_SESSION to an array, and then re-link it to
// the storage object.
//
// Additionally, while you _can_ write to $_SESSION following a
// session_write_close() operation, no changes made to it will be
// flushed to the session handler. As such, we now mark the storage
// object isImmutable.
$storage = $this->getStorage();
if (!$storage->isImmutable()) {
$_SESSION = $storage->toArray(true);
$this->saveHandler->writeMetadata(null, '_metadata');
$this->saveHandler->writeData($_SESSION['_data']);
session_write_close();
$storage->fromArray($_SESSION);
$storage->markImmutable();
}
}
}
MySessionStorage
<?php
namespace MySession\Model;
/* ...required use statements... */
class MySessionStorage extends SessionArrayStorage
{
/**
* Set storage metadata
*
* Metadata is used to store information about the data being stored in the
* object. Some example use cases include:
* - Setting expiry data
* - Maintaining access counts
* - localizing session storage
* - etc.
*
* @param string $key
* @param mixed $value
* @param bool $overwriteArray Whether to overwrite or merge array values; by default, merges
* @return ArrayStorage
* @throws Exception\RuntimeException
*/
public function setMetadata($key, $value, $overwriteArray = false)
{
if ($this->isImmutable()) {
throw new Exception\RuntimeException(
sprintf('Cannot set key "%s" as storage is marked isImmutable', $key)
);
}
// set the value
$sessVar = $_SESSION['_metadata'];
if (isset($sessVar[$key]) && is_array($value)) {
// data is array, check if we're replacing the whole array or modify/add to it
if ($overwriteArray) {
$sessVar[$key] = $value;
} else {
$sessVar[$key] = array_replace_recursive($sessVar[$key], $value);
}
} else {
// data is not an array, set or remove it in the session
if ((null === $value) && isset($sessVar[$key])) {
// remove data
$array = $sessVar;
unset($array[$key]);
$_SESSION[SessionVariableNames::$SESSION_METADATA] = $array; // we can't use $sessVar here because it's only a copy of $_SESSION
unset($array);
} elseif (null !== $value) {
// add data
$sessVar[$key] = $value;
}
}
return $this;
}
/**
* Retrieve metadata for the storage object or a specific metadata key
*
* Looks at session db for the metadata
*
* Returns false if no metadata stored, or no metadata exists for the given
* key.
*
* @param null|int|string $key
* @return mixed
*/
public function getMetadata($key = null)
{
if (!isset($_SESSION)) {
return false;
}
if (null === $key) {
return $_SESSION;
}
if (!array_key_exists($key, $_SESSION)) {
return false;
}
return $_SESSION[$key];
}
/**
* Set the request access time
*
* @param float $time
* @return ArrayStorage
*/
protected function setRequestAccessTime($time)
{
// make a metadata write call, since that sets a timestamp
$this->setMetadata('datatime', new DateTime("now"));
return $this;
}
}
MySessionSaveHandler
<?php
namespace MySession\Model;
/* ...required use statements... */
/**
* This class is the back end of the $_SESSION variable, when used together with a SessionStorage and SessionManager in a ZF module
*/
class MySessionSaveHandler implements SaveHandlerInterface
{
protected $sessionService;
private $tableGateway;
private $options;
private $sessionName;
private $sessionSavePath;
private $lifetime;
public function __construct(
TableGateway $tableGateway,
DbTableGatewayOptions $options,
ISessionService $sessionService)
{
$this->tableGateway = $tableGateway;
$this->options = $options;
$this->sessionService = $sessionService;
}
protected function getSessionService()
{
return $this->sessionService;
}
/**
* Read session data
*
* @param string $id
* @return string
*/
public function read($id)
{
// Get data from database
$metadata = $this->readMetadata($id);
// Put data in PHP-session-serialized form
$data = "_metadata|".serialize($metadata);
return $data;
}
/**
* Read session metadata
*
* @param string $id
* @return mixed
*/
public function readMetadata($id = null)
{
if (is_null($id))
{
if (!array_key_exists('sessionid', $_COOKIE))
{
// can't get id from cookie
return null;
}
$id = $_COOKIE['sessionid'];
}
if ($data = $this->getSessionService()->findById($id))
{
return $data->getArrayCopy();
}
return null;
}
/** deprecated, use writeMetadata instead
* Write session data
*
* @param string $id
* @param string $data
* @return bool
* Note sessions use an alternative serialization method.
*/
public function write($id, $data)
{
// don't use this because $data is serialized strangely and can't be automatically inserted into my table
}
/**
* Write session metadata
*
* @param string $id
* @param array $data an associative array matching a row in the table
* @return mixed
*/
public function writeMetadata($id = null, $data = null)
{
if (is_null($id))
{
if (!array_key_exists('sessionid', $_COOKIE))
{
// can't get id from cookie
return null;
}
$id = $_COOKIE['sessionid'];
}
// get the session info from the database so we can modify it
$sessionService = $this->getSessionService();
$session = $sessionService->findByID($id);
if (is_null($session)) {
$session = new \MyModule\Entity\MySession();
}
if (!is_null($data))
{
// overwrite the stored data
$session->setDataFromArray($data);
}
return $sessionService->save($session);
}
/**
* Destroy session - deletes data from session table
*
* @param string $id The session ID being destroyed.
* @return bool
* The return value (usually TRUE on success, FALSE on failure).
* Note this value is returned internally to PHP for processing.
*/
public function destroy($id)
{
$this->getSessionService()->delete($id);
return true;
}
/**
* Garbage Collection - cleanup old sessions
*
* @param int $maxlifetime
* Sessions that have not updated for
* the last maxlifetime seconds will be removed.
* @return bool
* The return value (usually TRUE on success, FALSE on failure).
* Note this value is returned internally to PHP for processing.
*/
public function gc($maxlifetime)
{
$metadata = $this->readMetadata(); // gets session id from cookie, then gets session from that
if (!is_null($metadata))
{
$datatime = $metadata['datatime'];
$previousTime = (new DateTime($datatime))->getTimestamp();
// if (current time - datatime) > maxlifetime, destroy the session
$val = time() - $previousTime;
if ($val > $maxlifetime) {
$this->destroy($metadata['sessionid']);
}
}
}
}
所有這一切的最終結果是,你可以簡單地通過訪問$ _SESSION變量訪問存儲在數據庫中的信息,因爲數據變從數據庫加載到bootstrap中的$ _SESSION變量中,並且在會話關閉時將$ _SESSION變量寫回到數據庫中(據我瞭解,當頁面發送給客戶端時)。