<?php
 
// Name: CachedArray.class.php
 
// Author: RReverser
 
// Description: Cached virtual array for PHP
 
// Version: 1.1
 
// Created: 27.03.2011
 
 
class CachedArrayIterator implements Iterator
 
{
 
    private $indexArray, $cachedArray;
 
    
 
    function __construct(CachedArray $ca)
 
    {
 
        $this->cachedArray = $ca;
 
        $this->indexArray = $ca->items();
 
    }
 
    
 
    function current() { return $this->valid() ? $this->cachedArray[$this->key()] : NULL; }
 
    function key() { return current($this->indexArray); }
 
    function next() { next($this->indexArray); return $this->current(); }
 
    function rewind() { reset($this->indexArray); return $this->current(); }
 
    function valid() { return key($this->indexArray) !== NULL; }
 
}
 
 
// Cached array class - implements full array-style or object-style access to elements
 
 
abstract class CachedArray implements ArrayAccess, Serializable, IteratorAggregate
 
{
 
    const sDELETED = -2, sEXISTS = -1, sUNCHANGED = 0, sMODIFIED = 1, sNEW = 2;
 
 
    protected $cache = array(), $addCount = 0, $listed = FALSE, $autoFlush = TRUE;
 
 
    // Your functions (for overriding)
 
    abstract function getItem($index);
 
    abstract function setItem($index, $value);
 
    abstract function addItem($value = NULL);
 
    function delItem($index) { $this->setItem($index, NULL); }
 
    function hasItem($index) { return $this[$index] !== NULL; }
 
    function getList() { return array(); }
 
 
    function __construct($autoFlush = TRUE) { $this->autoFlush = $autoFlush; }
 
    function __destruct() { $this->autoFlush ? $this->flush() : FALSE; }
 
 
    // Functions for object-style access
 
    function __get($index) { return $this[$index]; }
 
    function __set($index, $value) { $this[$index] = $value; }
 
    function __isset($index) { return isset($this[$index]); }
 
    function __unset($index) { unset($this[$index]); }
 
 
    // Functions for array-style access
 
    function offsetGet($index)
 
    {
 
        if (!isset($this->cache[$index]) || $this->cache[$index][0] == self::sEXISTS)
 
        {
 
            $this->cache[$index] = array(self::sUNCHANGED, $this->getItem($index));
 
        }
 
 
        return $this->cache[$index][0] >= 0 ? $this->cache[$index][1] : NULL;
 
    }
 
 
    function offsetSet($index, $value)
 
    {
 
        if ($index === NULL)
 
        {
 
            $index = 'new_' . $this->addCount++;
 
            $State = self::sNEW;
 
        } else
 
        {
 
            $State = isset($this[$index]) ? self::sMODIFIED : self::sNEW;
 
        }
 
 
        $this->cache[$index] = array($State, $value);
 
    }
 
 
    function offsetExists($index)
 
    {
 
        if (isset($this->cache[$index]) && $this->cache[$index][0] != self::sDELETED)
 
        {
 
            return isset($this->cache[$index][1]);
 
        } else
 
        {
 
            $exists = $this->hasItem($index);
 
            $this->cache[$index] = array($exists ? self::sEXISTS : self::sUNCHANGED, NULL);
 
            return $exists;
 
        }
 
    }
 
 
    function offsetUnset($index)
 
    {
 
        $this->cache[$index][0] = self::sDELETED;
 
    }
 
 
    // Functions for serialization
 
    function serialize() { return serialize(array($this->cache, $this->addCount, $this->listed, $this->autoFlush)); }
 
    function unserialize($str) { list($this->cache, $this->addCount, $this->listed, $this->autoFlush) = unserialize($str); }
 
 
    // Returning iterator object
 
    function getIterator() { return new CachedArrayIterator($this); }
 
 
    // Flushing procedure
 
    function flush()
 
    {
 
        $movements = array();
 
 
        foreach ($this->cache as $index => &$data)
 
        {
 
            switch ($data[0])
 
            {
 
                case self::sDELETED:
 
                    $this->delItem($index);
 
                    $data = array(self::sUNCHANGED, NULL);
 
                    break;
 
                
 
                case self::sMODIFIED:
 
                    $this->setItem($index, $data[1]);
 
                    $data[0] = self::sUNCHANGED;
 
                    break;
 
                
 
                case self::sNEW:
 
                    $newIndex = $this->addItem($data[1]);
 
                    $movements[$index] = $newIndex;
 
                    break;
 
            }
 
        }
 
 
        foreach ($movements as $from => $to)
 
        {
 
            $this->cache[$to] = array(self::sUNCHANGED, $this->cache[$from][1]);
 
            unset($this->cache[$from]);
 
        }
 
 
        $this->addCount = 0;
 
    }
 
 
    function drop()
 
    {
 
        $this->cache = array();
 
        $this->addCount = 0;
 
        $this->listed = FALSE;
 
    }
 
 
    // Listing of real and unflushed items
 
    function items()
 
    {
 
        if (!$this->listed)
 
        {
 
            $result = $this->getList();
 
 
            foreach ($result as $innerIndex => $index)
 
            {
 
                if (!isset($this->cache[$index])) { $this->cache[$index] = array(self::sEXISTS, NULL); }
 
                elseif ($this->cache[$index][0] == self::sDELETED) { unset($result[$innerIndex]); }
 
            }
 
            
 
            foreach ($this->cache as $index => $data)
 
            {
 
                if ($data[0] == self::sNEW) { $result[] = $index; }
 
            }
 
 
            $this->listed = TRUE;
 
        }
 
        else
 
        {
 
            $result = array();
 
            
 
            foreach ($this->cache as $index => $data)
 
            {
 
                if ($data[0] >= self::sEXISTS) { $result[] = $index; }
 
            }
 
        }
 
        
 
        return $result;
 
    }
 
 
}
 
 |