<?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\Storage;
use Zend\Stdlib\ArrayObject;
use Zend\Session\Exception;
/**
* Array session storage
*
* Defines an ArrayObject interface for accessing session storage, with options
* for setting metadata, locking, and marking as isImmutable.
*/
class ArrayStorage extends ArrayObject implements StorageInterface
{
/**
* Is storage marked isImmutable?
* @var bool
*/
protected $isImmutable = false;
/**
* Constructor
*
* Instantiates storage as an ArrayObject, allowing property access.
* Also sets the initial request access time.
*
* @param array $input
* @param int $flags
* @param string $iteratorClass
*/
public function __construct(
$input = array(),
$flags = ArrayObject::ARRAY_AS_PROPS,
$iteratorClass = '\\ArrayIterator'
) {
parent::__construct($input, $flags, $iteratorClass);
$this->setRequestAccessTime(microtime(true));
}
/**
* Set the request access time
*
* @param float $time
* @return ArrayStorage
*/
protected function setRequestAccessTime($time)
{
$this->setMetadata('_REQUEST_ACCESS_TIME', $time);
return $this;
}
/**
* Retrieve the request access time
*
* @return float
*/
public function getRequestAccessTime()
{
return $this->getMetadata('_REQUEST_ACCESS_TIME');
}
/**
* Set a value in the storage object
*
* If the object is marked as isImmutable, or the object or key is marked as
* locked, raises an exception.
*
* @param string $key
* @param mixed $value
* @return void
*/
/**
* @param mixed $key
* @param mixed $value
* @throws Exception\RuntimeException
*/
public function offsetSet($key, $value)
{
if ($this->isImmutable()) {
throw new Exception\RuntimeException(sprintf(
'Cannot set key "%s" as storage is marked isImmutable', $key
));
}
if ($this->isLocked($key)) {
throw new Exception\RuntimeException(sprintf(
'Cannot set key "%s" due to locking', $key
));
}
parent::offsetSet($key, $value);
}
/**
* Lock this storage instance, or a key within it
*
* @param null|int|string $key
* @return ArrayStorage
*/
public function lock($key = null)
{
if (null === $key) {
$this->setMetadata('_READONLY', true);
return $this;
}
if (isset($this[$key])) {
$this->setMetadata('_LOCKS', array($key => true));
}
return $this;
}
/**
* Is the object or key marked as locked?
*
* @param null|int|string $key
* @return bool
*/
public function isLocked($key = null)
{
if ($this->isImmutable()) {
// isImmutable trumps all
return true;
}
if (null === $key) {
// testing for global lock
return $this->getMetadata('_READONLY');
}
$locks = $this->getMetadata('_LOCKS');
$readOnly = $this->getMetadata('_READONLY');
if ($readOnly && !$locks) {
// global lock in play; all keys are locked
return true;
} elseif ($readOnly && $locks) {
return array_key_exists($key, $locks);
}
// test for individual locks
if (!$locks) {
return false;
}
return array_key_exists($key, $locks);
}
/**
* Unlock an object or key marked as locked
*
* @param null|int|string $key
* @return ArrayStorage
*/
public function unlock($key = null)
{
if (null === $key) {
// Unlock everything
$this->setMetadata('_READONLY', false);
$this->setMetadata('_LOCKS', false);
return $this;
}
$locks = $this->getMetadata('_LOCKS');
if (!$locks) {
if (!$this->getMetadata('_READONLY')) {
return $this;
}
$array = $this->toArray();
$keys = array_keys($array);
$locks = array_flip($keys);
unset($array, $keys);
}
if (array_key_exists($key, $locks)) {
unset($locks[$key]);
$this->setMetadata('_LOCKS', $locks, true);
}
return $this;
}
/**
* Mark the storage container as isImmutable
*
* @return ArrayStorage
*/
public function markImmutable()
{
$this->isImmutable = true;
return $this;
}
/**
* Is the storage container marked as isImmutable?
*
* @return bool
*/
public function isImmutable()
{
return $this->isImmutable;
}
/**
* 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
));
}
if (!isset($this['__ZF'])) {
$this['__ZF'] = array();
}
if (isset($this['__ZF'][$key]) && is_array($value)) {
if ($overwriteArray) {
$this['__ZF'][$key] = $value;
} else {
$this['__ZF'][$key] = array_replace_recursive($this['__ZF'][$key], $value);
}
} else {
if ((null === $value) && isset($this['__ZF'][$key])) {
// unset($this['__ZF'][$key]) led to "indirect modification...
// has no effect" errors, so explicitly pulling array and
// unsetting key.
$array = $this['__ZF'];
unset($array[$key]);
$this['__ZF'] = $array;
unset($array);
} elseif (null !== $value) {
$this['__ZF'][$key] = $value;
}
}
return $this;
}
/**
* Retrieve metadata for the storage object or a specific metadata key
*
* 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($this['__ZF'])) {
return false;
}
if (null === $key) {
return $this['__ZF'];
}
if (!array_key_exists($key, $this['__ZF'])) {
return false;
}
return $this['__ZF'][$key];
}
/**
* Clear the storage object or a subkey of the object
*
* @param null|int|string $key
* @return ArrayStorage
* @throws Exception\RuntimeException
*/
public function clear($key = null)
{
if ($this->isImmutable()) {
throw new Exception\RuntimeException('Cannot clear storage as it is marked immutable');
}
if (null === $key) {
$this->fromArray(array());
return $this;
}
if (!isset($this[$key])) {
return $this;
}
// Clear key data
unset($this[$key]);
// Clear key metadata
$this->setMetadata($key, null)
->unlock($key);
return $this;
}
/**
* Load the storage from another array
*
* Overwrites any data that was previously set.
*
* @param array $array
* @return ArrayStorage
*/
public function fromArray(array $array)
{
$ts = $this->getRequestAccessTime();
$this->exchangeArray($array);
$this->setRequestAccessTime($ts);
return $this;
}
/**
* Cast the object to an array
*
* @param bool $metaData Whether to include metadata
* @return array
*/
public function toArray($metaData = false)
{
$values = $this->getArrayCopy();
if ($metaData) {
return $values;
}
if (isset($values['__ZF'])) {
unset($values['__ZF']);
}
return $values;
}
}