mirror of
https://github.com/torrentpier/torrentpier-lts.git
synced 2025-03-01 15:21:02 +03:00
615 lines
18 KiB
PHP
615 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Zend Framework (http://framework.zend.com/)
|
|
*
|
|
* @link http://github.com/zendframework/zf2 for the canonical source repository
|
|
* @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
*/
|
|
|
|
namespace Zend\Session;
|
|
|
|
use ArrayIterator;
|
|
use Iterator;
|
|
use Traversable;
|
|
use Zend\Session\ManagerInterface as Manager;
|
|
use Zend\Session\Storage\StorageInterface as Storage;
|
|
use Zend\Stdlib\ArrayObject;
|
|
|
|
/**
|
|
* Session storage container
|
|
*
|
|
* Allows for interacting with session storage in isolated containers, which
|
|
* may have their own expiries, or even expiries per key in the container.
|
|
* Additionally, expiries may be absolute TTLs or measured in "hops", which
|
|
* are based on how many times the key or container were accessed.
|
|
*/
|
|
abstract class AbstractContainer extends ArrayObject
|
|
{
|
|
/**
|
|
* Container name
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $name;
|
|
|
|
/**
|
|
* @var Manager
|
|
*/
|
|
protected $manager;
|
|
|
|
/**
|
|
* Default manager class to use if no manager has been provided
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $managerDefaultClass = 'Zend\\Session\\SessionManager';
|
|
|
|
/**
|
|
* Default manager to use when instantiating a container without providing a ManagerInterface
|
|
*
|
|
* @var Manager
|
|
*/
|
|
protected static $defaultManager;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Provide a name ('Default' if none provided) and a ManagerInterface instance.
|
|
*
|
|
* @param null|string $name
|
|
* @param Manager $manager
|
|
* @throws Exception\InvalidArgumentException
|
|
*/
|
|
public function __construct($name = 'Default', Manager $manager = null)
|
|
{
|
|
if (!preg_match('/^[a-z][a-z0-9_\\\]+$/i', $name)) {
|
|
throw new Exception\InvalidArgumentException(
|
|
'Name passed to container is invalid; must consist of alphanumerics, backslashes and underscores only'
|
|
);
|
|
}
|
|
$this->name = $name;
|
|
$this->setManager($manager);
|
|
|
|
// Create namespace
|
|
parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS);
|
|
|
|
// Start session
|
|
$this->getManager()->start();
|
|
}
|
|
|
|
/**
|
|
* Set the default ManagerInterface instance to use when none provided to constructor
|
|
*
|
|
* @param Manager $manager
|
|
* @return void
|
|
*/
|
|
public static function setDefaultManager(Manager $manager = null)
|
|
{
|
|
static::$defaultManager = $manager;
|
|
}
|
|
|
|
/**
|
|
* Get the default ManagerInterface instance
|
|
*
|
|
* If none provided, instantiates one of type {@link $managerDefaultClass}
|
|
*
|
|
* @return Manager
|
|
* @throws Exception\InvalidArgumentException if invalid manager default class provided
|
|
*/
|
|
public static function getDefaultManager()
|
|
{
|
|
if (null === static::$defaultManager) {
|
|
$manager = new static::$managerDefaultClass();
|
|
if (!$manager instanceof Manager) {
|
|
throw new Exception\InvalidArgumentException(
|
|
'Invalid default manager type provided; must implement ManagerInterface'
|
|
);
|
|
}
|
|
static::$defaultManager = $manager;
|
|
}
|
|
|
|
return static::$defaultManager;
|
|
}
|
|
|
|
/**
|
|
* Get container name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getName()
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Set session manager
|
|
*
|
|
* @param null|Manager $manager
|
|
* @return Container
|
|
* @throws Exception\InvalidArgumentException
|
|
*/
|
|
protected function setManager(Manager $manager = null)
|
|
{
|
|
if (null === $manager) {
|
|
$manager = static::getDefaultManager();
|
|
if (!$manager instanceof Manager) {
|
|
throw new Exception\InvalidArgumentException(
|
|
'Manager provided is invalid; must implement ManagerInterface'
|
|
);
|
|
}
|
|
}
|
|
$this->manager = $manager;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get manager instance
|
|
*
|
|
* @return Manager
|
|
*/
|
|
public function getManager()
|
|
{
|
|
return $this->manager;
|
|
}
|
|
|
|
/**
|
|
* Get session storage object
|
|
*
|
|
* Proxies to ManagerInterface::getStorage()
|
|
*
|
|
* @return Storage
|
|
*/
|
|
protected function getStorage()
|
|
{
|
|
return $this->getManager()->getStorage();
|
|
}
|
|
|
|
/**
|
|
* Create a new container object on which to act
|
|
*
|
|
* @return ArrayObject
|
|
*/
|
|
protected function createContainer()
|
|
{
|
|
return new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
|
|
}
|
|
|
|
/**
|
|
* Verify container namespace
|
|
*
|
|
* Checks to see if a container exists within the Storage object already.
|
|
* If not, one is created; if so, checks to see if it's an ArrayObject.
|
|
* If not, it raises an exception; otherwise, it returns the Storage
|
|
* object.
|
|
*
|
|
* @param bool $createContainer Whether or not to create the container for the namespace
|
|
* @return Storage|null Returns null only if $createContainer is false
|
|
* @throws Exception\RuntimeException
|
|
*/
|
|
protected function verifyNamespace($createContainer = true)
|
|
{
|
|
$storage = $this->getStorage();
|
|
$name = $this->getName();
|
|
if (!isset($storage[$name])) {
|
|
if (!$createContainer) {
|
|
return;
|
|
}
|
|
$storage[$name] = $this->createContainer();
|
|
}
|
|
if (!is_array($storage[$name]) && !$storage[$name] instanceof Traversable) {
|
|
throw new Exception\RuntimeException('Container cannot write to storage due to type mismatch');
|
|
}
|
|
|
|
return $storage;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a given key needs to be expired
|
|
*
|
|
* Returns true if the key has expired, false otherwise.
|
|
*
|
|
* @param null|string $key
|
|
* @return bool
|
|
*/
|
|
protected function expireKeys($key = null)
|
|
{
|
|
$storage = $this->verifyNamespace();
|
|
$name = $this->getName();
|
|
|
|
// Return early if key not found
|
|
if ((null !== $key) && !isset($storage[$name][$key])) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->expireByExpiryTime($storage, $name, $key)) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->expireByHops($storage, $name, $key)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Expire a key by expiry time
|
|
*
|
|
* Checks to see if the entire container has expired based on TTL setting,
|
|
* or the individual key.
|
|
*
|
|
* @param Storage $storage
|
|
* @param string $name Container name
|
|
* @param string $key Key in container to check
|
|
* @return bool
|
|
*/
|
|
protected function expireByExpiryTime(Storage $storage, $name, $key)
|
|
{
|
|
$metadata = $storage->getMetadata($name);
|
|
|
|
// Global container expiry
|
|
if (is_array($metadata)
|
|
&& isset($metadata['EXPIRE'])
|
|
&& ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE'])
|
|
) {
|
|
unset($metadata['EXPIRE']);
|
|
$storage->setMetadata($name, $metadata, true);
|
|
$storage[$name] = $this->createContainer();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Expire individual key
|
|
if ((null !== $key)
|
|
&& is_array($metadata)
|
|
&& isset($metadata['EXPIRE_KEYS'])
|
|
&& isset($metadata['EXPIRE_KEYS'][$key])
|
|
&& ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE_KEYS'][$key])
|
|
) {
|
|
unset($metadata['EXPIRE_KEYS'][$key]);
|
|
$storage->setMetadata($name, $metadata, true);
|
|
unset($storage[$name][$key]);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Find any keys that have expired
|
|
if ((null === $key)
|
|
&& is_array($metadata)
|
|
&& isset($metadata['EXPIRE_KEYS'])
|
|
) {
|
|
foreach (array_keys($metadata['EXPIRE_KEYS']) as $key) {
|
|
if ($_SERVER['REQUEST_TIME'] > $metadata['EXPIRE_KEYS'][$key]) {
|
|
unset($metadata['EXPIRE_KEYS'][$key]);
|
|
if (isset($storage[$name][$key])) {
|
|
unset($storage[$name][$key]);
|
|
}
|
|
}
|
|
}
|
|
$storage->setMetadata($name, $metadata, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Expire key by session hops
|
|
*
|
|
* Determines whether the container or an individual key within it has
|
|
* expired based on session hops
|
|
*
|
|
* @param Storage $storage
|
|
* @param string $name
|
|
* @param string $key
|
|
* @return bool
|
|
*/
|
|
protected function expireByHops(Storage $storage, $name, $key)
|
|
{
|
|
$ts = $storage->getRequestAccessTime();
|
|
$metadata = $storage->getMetadata($name);
|
|
|
|
// Global container expiry
|
|
if (is_array($metadata)
|
|
&& isset($metadata['EXPIRE_HOPS'])
|
|
&& ($ts > $metadata['EXPIRE_HOPS']['ts'])
|
|
) {
|
|
$metadata['EXPIRE_HOPS']['hops']--;
|
|
if (-1 === $metadata['EXPIRE_HOPS']['hops']) {
|
|
unset($metadata['EXPIRE_HOPS']);
|
|
$storage->setMetadata($name, $metadata, true);
|
|
$storage[$name] = $this->createContainer();
|
|
|
|
return true;
|
|
}
|
|
$metadata['EXPIRE_HOPS']['ts'] = $ts;
|
|
$storage->setMetadata($name, $metadata, true);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Single key expiry
|
|
if ((null !== $key)
|
|
&& is_array($metadata)
|
|
&& isset($metadata['EXPIRE_HOPS_KEYS'])
|
|
&& isset($metadata['EXPIRE_HOPS_KEYS'][$key])
|
|
&& ($ts > $metadata['EXPIRE_HOPS_KEYS'][$key]['ts'])
|
|
) {
|
|
$metadata['EXPIRE_HOPS_KEYS'][$key]['hops']--;
|
|
if (-1 === $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']) {
|
|
unset($metadata['EXPIRE_HOPS_KEYS'][$key]);
|
|
$storage->setMetadata($name, $metadata, true);
|
|
unset($storage[$name][$key]);
|
|
|
|
return true;
|
|
}
|
|
$metadata['EXPIRE_HOPS_KEYS'][$key]['ts'] = $ts;
|
|
$storage->setMetadata($name, $metadata, true);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Find all expired keys
|
|
if ((null === $key)
|
|
&& is_array($metadata)
|
|
&& isset($metadata['EXPIRE_HOPS_KEYS'])
|
|
) {
|
|
foreach (array_keys($metadata['EXPIRE_HOPS_KEYS']) as $key) {
|
|
if ($ts > $metadata['EXPIRE_HOPS_KEYS'][$key]['ts']) {
|
|
$metadata['EXPIRE_HOPS_KEYS'][$key]['hops']--;
|
|
if (-1 === $metadata['EXPIRE_HOPS_KEYS'][$key]['hops']) {
|
|
unset($metadata['EXPIRE_HOPS_KEYS'][$key]);
|
|
$storage->setMetadata($name, $metadata, true);
|
|
unset($storage[$name][$key]);
|
|
continue;
|
|
}
|
|
$metadata['EXPIRE_HOPS_KEYS'][$key]['ts'] = $ts;
|
|
}
|
|
}
|
|
$storage->setMetadata($name, $metadata, true);
|
|
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Store a value within the container
|
|
*
|
|
* @param string $key
|
|
* @param mixed $value
|
|
* @return void
|
|
*/
|
|
public function offsetSet($key, $value)
|
|
{
|
|
$this->expireKeys($key);
|
|
$storage = $this->verifyNamespace();
|
|
$name = $this->getName();
|
|
$storage[$name][$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* Determine if the key exists
|
|
*
|
|
* @param string $key
|
|
* @return bool
|
|
*/
|
|
public function offsetExists($key)
|
|
{
|
|
// If no container exists, we can't inspect it
|
|
if (null === ($storage = $this->verifyNamespace(false))) {
|
|
return false;
|
|
}
|
|
$name = $this->getName();
|
|
|
|
// Return early if the key isn't set
|
|
if (!isset($storage[$name][$key])) {
|
|
return false;
|
|
}
|
|
|
|
$expired = $this->expireKeys($key);
|
|
|
|
return !$expired;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a specific key in the container
|
|
*
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
public function offsetGet($key)
|
|
{
|
|
if (!$this->offsetExists($key)) {
|
|
return null;
|
|
}
|
|
$storage = $this->getStorage();
|
|
$name = $this->getName();
|
|
|
|
return $storage[$name][$key];
|
|
}
|
|
|
|
/**
|
|
* Unset a single key in the container
|
|
*
|
|
* @param string $key
|
|
* @return void
|
|
*/
|
|
public function offsetUnset($key)
|
|
{
|
|
if (!$this->offsetExists($key)) {
|
|
return;
|
|
}
|
|
$storage = $this->getStorage();
|
|
$name = $this->getName();
|
|
unset($storage[$name][$key]);
|
|
}
|
|
|
|
/**
|
|
* Exchange the current array with another array or object.
|
|
*
|
|
* @param array|object $input
|
|
* @return array Returns the old array
|
|
* @see ArrayObject::exchangeArray()
|
|
*/
|
|
public function exchangeArray($input)
|
|
{
|
|
// handle arrayobject, iterators and the like:
|
|
if (is_object($input) && ($input instanceof ArrayObject || $input instanceof \ArrayObject)) {
|
|
$input = $input->getArrayCopy();
|
|
}
|
|
if (!is_array($input)) {
|
|
$input = (array) $input;
|
|
}
|
|
|
|
$storage = $this->verifyNamespace();
|
|
$name = $this->getName();
|
|
|
|
$old = $storage[$name];
|
|
$storage[$name] = $input;
|
|
if ($old instanceof ArrayObject) {
|
|
return $old->getArrayCopy();
|
|
}
|
|
|
|
return $old;
|
|
}
|
|
|
|
/**
|
|
* Iterate over session container
|
|
*
|
|
* @return Iterator
|
|
*/
|
|
public function getIterator()
|
|
{
|
|
$this->expireKeys();
|
|
$storage = $this->getStorage();
|
|
$container = $storage[$this->getName()];
|
|
|
|
if ($container instanceof Traversable) {
|
|
return $container;
|
|
}
|
|
|
|
return new ArrayIterator($container);
|
|
}
|
|
|
|
/**
|
|
* Set expiration TTL
|
|
*
|
|
* Set the TTL for the entire container, a single key, or a set of keys.
|
|
*
|
|
* @param int $ttl TTL in seconds
|
|
* @param string|array|null $vars
|
|
* @return Container
|
|
* @throws Exception\InvalidArgumentException
|
|
*/
|
|
public function setExpirationSeconds($ttl, $vars = null)
|
|
{
|
|
$storage = $this->getStorage();
|
|
$ts = $_SERVER['REQUEST_TIME'] + $ttl;
|
|
if (is_scalar($vars) && null !== $vars) {
|
|
$vars = (array) $vars;
|
|
}
|
|
|
|
if (null === $vars) {
|
|
$this->expireKeys(); // first we need to expire global key, since it can already be expired
|
|
$data = array('EXPIRE' => $ts);
|
|
} elseif (is_array($vars)) {
|
|
// Cannot pass "$this" to a lambda
|
|
$container = $this;
|
|
|
|
// Filter out any items not in our container
|
|
$expires = array_filter($vars, function ($value) use ($container) {
|
|
return $container->offsetExists($value);
|
|
});
|
|
|
|
// Map item keys => timestamp
|
|
$expires = array_flip($expires);
|
|
$expires = array_map(function ($value) use ($ts) {
|
|
return $ts;
|
|
}, $expires);
|
|
|
|
// Create metadata array to merge in
|
|
$data = array('EXPIRE_KEYS' => $expires);
|
|
} else {
|
|
throw new Exception\InvalidArgumentException(
|
|
'Unknown data provided as second argument to ' . __METHOD__
|
|
);
|
|
}
|
|
|
|
$storage->setMetadata(
|
|
$this->getName(),
|
|
$data
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set expiration hops for the container, a single key, or set of keys
|
|
*
|
|
* @param int $hops
|
|
* @param null|string|array $vars
|
|
* @throws Exception\InvalidArgumentException
|
|
* @return Container
|
|
*/
|
|
public function setExpirationHops($hops, $vars = null)
|
|
{
|
|
$storage = $this->getStorage();
|
|
$ts = $storage->getRequestAccessTime();
|
|
|
|
if (is_scalar($vars) && (null !== $vars)) {
|
|
$vars = (array) $vars;
|
|
}
|
|
|
|
if (null === $vars) {
|
|
$this->expireKeys(); // first we need to expire global key, since it can already be expired
|
|
$data = array('EXPIRE_HOPS' => array('hops' => $hops, 'ts' => $ts));
|
|
} elseif (is_array($vars)) {
|
|
// Cannot pass "$this" to a lambda
|
|
$container = $this;
|
|
|
|
// FilterInterface out any items not in our container
|
|
$expires = array_filter($vars, function ($value) use ($container) {
|
|
return $container->offsetExists($value);
|
|
});
|
|
|
|
// Map item keys => timestamp
|
|
$expires = array_flip($expires);
|
|
$expires = array_map(function ($value) use ($hops, $ts) {
|
|
return array('hops' => $hops, 'ts' => $ts);
|
|
}, $expires);
|
|
|
|
// Create metadata array to merge in
|
|
$data = array('EXPIRE_HOPS_KEYS' => $expires);
|
|
} else {
|
|
throw new Exception\InvalidArgumentException(
|
|
'Unknown data provided as second argument to ' . __METHOD__
|
|
);
|
|
}
|
|
|
|
$storage->setMetadata(
|
|
$this->getName(),
|
|
$data
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Creates a copy of the specific container name
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getArrayCopy()
|
|
{
|
|
$storage = $this->verifyNamespace();
|
|
$container = $storage[$this->getName()];
|
|
|
|
return $container instanceof ArrayObject ? $container->getArrayCopy() : $container;
|
|
}
|
|
}
|