Source for file FTCache.php

Documentation is available at FTCache.php

  1. <?php
  2. /**
  3.  * A collection of classes and interfaces to be used in implementing caching of objects.
  4.  * 
  5.  * This system is incredibly flexible in that it allows us to use a variety of strategies
  6.  * for caching along with a variety of storage mechanisms. Strategies are basically methods
  7.  * to determine how to map objects to their cache entries. Containers allow us to customize
  8.  * where the data is stored (file system, ram, mem_cached, database, etc).
  9.  * 
  10.  * @author Justin DeMaris
  11.  * @package FTCache
  12.  */
  13.  
  14. /**
  15.  * The center of the caching system. This handles cacheable objects and stores
  16.  * them in the chosen container mechanism using the given algorithm.
  17.  *
  18.  * @author Justin DeMaris
  19.  * @package FTCache
  20.  */
  21. class FTCache {
  22.     /**
  23.      * Algorithm we use to map object ids and cache indices
  24.      *
  25.      * @var FTCache_Strategy 
  26.      */
  27.     protected $_strategy = NULL;
  28.     
  29.     /**
  30.      * Container where the cache entries are stored
  31.      *
  32.      * @var FTCache_Container 
  33.      */
  34.     protected $_container = NULL;
  35.     
  36.     /**
  37.      * Creates a new cache with the given options.
  38.      *
  39.      * @param int $size 
  40.      * @param string|FTCache_Strategy$algorithm Name or Instance of an FTCache_Strategy class to use
  41.      * @param string|FTCache_Container$container Name or Instance of an FTCache_Container class to use
  42.      * @throws Exception Throws an exception if we have invalid parameter
  43.      */
  44.     public function __construct($size$algorithm$container{
  45.         // Enforce valid size
  46.         if !is_numeric($size|| $size <= 0 )
  47.             throw new Exception("Invalid Size");
  48.         
  49.         // If $algorithm is already an instance of strategy, then use the object
  50.         if $algorithm instanceof FTCache_Strategy )
  51.             $this->_strategy = $algorithm;
  52.         // Otherwise, validate and instantiate
  53.         else {
  54.             $algorithm 'FTCache_Strategy_'.$algorithm;
  55.             
  56.             // Enforce valid algorithm choice
  57.             if !class_exists($algorithm|| !is_subclass_of($algorithm'FTCache_Strategy') )
  58.                 throw new Exception("Invalid Algorithm");
  59.             
  60.             // Create the strategy maintainer
  61.             $this->_strategy = new $algorithm($size);
  62.         }
  63.         
  64.         // If $container is already an instance of container, then use the object
  65.         if $container instanceof FTCache_Container )
  66.             $this->_container = $container;
  67.         // Otherwise, validate and instantiate
  68.         else {
  69.             // Rewrite some rules
  70.             $container 'FTCache_Container_'.$container;
  71.             
  72.             // Enforce valid container choice
  73.             if !class_exists($container|| !is_subclass_of($container'FTCache_Container') )
  74.                 throw new Exception("Invalid Container");
  75.     
  76.             // Create the container
  77.             $this->_container = new $container($size);
  78.         }
  79.     }
  80.     
  81.     /**
  82.      * Retrieve the object with the given index from the cache.
  83.      *
  84.      * @param int $id Object ID
  85.      * @param FTCache_Cacheable $object Object to deserialize into
  86.      * @return boolean True on success, false on error.
  87.      */
  88.     public function get($id$object{
  89.         // Make sure that the object is actually set
  90.         if !$this->isCached($id) ) return false;
  91.         
  92.         // Map object id to cache index
  93.         $index $this->_strategy->toCache($id);
  94.         
  95.         // Exit on mapping error
  96.         if $index === false return false;
  97.         
  98.         // Retrieve the data from the cache
  99.         $result $this->_container->get($index);
  100.         
  101.         // Exit on cache grab error
  102.         if $result === false return false;
  103.         
  104.         // Otherwise, deserialize
  105.         $object->deserialize($result);
  106.         
  107.         // And return success
  108.         return true;
  109.     }
  110.  
  111.     /**
  112.      * Check if the given index is in the cache
  113.      *
  114.      * @param int $id Object ID
  115.      * @return boolean True on cached, false on not cached
  116.      */
  117.     public function isCached($id{
  118.         // Map object id to cache index
  119.         $index $this->_strategy->toCache($id);
  120.         
  121.         // Return no if we can't map it
  122.         if $index === false return false;
  123.  
  124.         // Check the cache for the object
  125.         $result $this->_container->get($index);
  126.         
  127.         // If error, then not cached
  128.         if $result === false return false;
  129.  
  130.         // Reverse the map to make sure that the object is the same coming out
  131.         $cached_id $this->_strategy->fromCache($index);
  132.         
  133.         // If cached entry id does not match given id, then fail
  134.         return $cached_id == $id );
  135.     }
  136.     
  137.     /**
  138.      * Place the given object into the cache
  139.      *
  140.      * @param FTCache_Cacheable $object Object to cache
  141.      * @return boolean True on success, false on error.
  142.      */
  143.     public function set(FTCache_Cacheable $object{
  144.         // Get object id
  145.         $id $object->getIndex();
  146.         
  147.         // Enforce valid object id
  148.         if !is_numeric($id) ) return false;
  149.         
  150.         // Convert the object id to a cache index
  151.         $index $this->_strategy->toCache($id);
  152.         
  153.         // Exit on mapping error
  154.         if $index === false return false;
  155.  
  156.         // Serialize the object
  157.         $data $object->serialize();
  158.         
  159.         // Store it into the cache
  160.         $result $this->_container->set($index$data);
  161.  
  162.         // If error, exit
  163.         if $result === false return false;
  164.         
  165.         // Tell the strategy that it has been stored
  166.         $this->_strategy->stored($object->getIndex());
  167.         
  168.         // Return success
  169.         return true;
  170.     }
  171. }
  172.  
  173. /**
  174.  * Interface for any object that is cacheable by this caching scheme.
  175.  *
  176.  * @author Justin DeMaris
  177.  * @package FTCache
  178.  */
  179. interface FTCache_Cacheable {
  180.     /**
  181.      * Get a unique identifier for this particular object.
  182.      *
  183.      * @return int Identifier
  184.      */
  185.     public function getIndex();
  186.     
  187.     /**
  188.      * Convert the object to a string of data that represents its entire state.
  189.      *
  190.      * @return string Serialization of object
  191.      */
  192.     public function serialize();
  193.     
  194.     /**
  195.      * Convert the current object to be a the object pulled from cache.
  196.      *
  197.      * @param string $data Serialization of object
  198.      * @return boolean True on success, false on error.
  199.      */
  200.     public function deserialize($data);
  201. }
  202.  
  203. /**
  204.  * Describes the functions that we will need to implement to provide a mapping between
  205.  * the objects ID and the ID used in the cache.
  206.  * 
  207.  * NOTE: calling fromCache(toCache($some_id)) will not necessarily return $some_id! This
  208.  *       is because toCache is used to map both already cached id's and not yet cached ids
  209.  *       while fromCache is used to only return the id of already cached entries.
  210.  *
  211.  * @author Justin DeMaris
  212.  * @package FTCache
  213.  */
  214. abstract class FTCache_Strategy {
  215.     /**
  216.      * Size of the schema, used for the modulus
  217.      *
  218.      * @var int 
  219.      */
  220.     protected $_size = 0;
  221.     
  222.     /**
  223.      * Sets the size of the cache.
  224.      *
  225.      * @param int $size Size of the cache
  226.      */
  227.     public function __construct($size{
  228.         // Enforce valid size
  229.         if !is_numeric($size|| $size <= 0 )
  230.             throw new Exception("Invalid Size");
  231.         
  232.         // Save the size
  233.         $this->_size = $size;
  234.     }
  235.  
  236.     /**
  237.      * Convert a cache entry index into an object id
  238.      * 
  239.      * This method must return the object id of the cache entry stored at the given index.
  240.      * If there is nothing stored there, then it must return false.
  241.      *
  242.      * @param int $index Cache entry index
  243.      * @return int Object Id. False if we can't map that index or nothing is stored there.
  244.      */
  245.     public abstract function fromCache($index);
  246.  
  247.     /**
  248.      * Whenever the cache will store an object in the container, it informs this strategy
  249.      * object by calling this method.
  250.      *
  251.      * @param int $id Object id
  252.      */
  253.     public abstract function stored($id);
  254.     
  255.     /**
  256.      * Convert an object ID into a cache entry index.
  257.      * 
  258.      * This method must return the index that the given object id is stored at if it is
  259.      * currently stored, or the index that it will be stored at if it is not already stored.
  260.      * 
  261.      * @param int $id Object ID
  262.      * @return int Cache Index. False if we can't / won't map that index
  263.      */
  264.     public abstract function toCache($id);
  265. }
  266.  
  267. /**
  268.  * This describes the way the functions that we will need to implement to store the given
  269.  * data into the cache.
  270.  *
  271.  * @author Justin DeMaris
  272.  * @package FTCache
  273.  */
  274. abstract class FTCache_Container {
  275.     /**
  276.      * Size of the schema
  277.      *
  278.      * @var int 
  279.      */
  280.     protected $_size = 0;
  281.     
  282.     /**
  283.      * Sets the size of the cache
  284.      *
  285.      * @param int $size Number of entries to store in the container at once
  286.      */
  287.     public function __construct($size{
  288.         // Enforce valid size
  289.         if !is_numeric($size|| $size <= 0 )
  290.             throw new Exception("Invalid Size");
  291.         
  292.         // Save the size
  293.         $this->_size = $size;
  294.     }
  295.     
  296.     /**
  297.      * Retrieve the string of stored data at the given cache index
  298.      *
  299.      * @param int $index Cache index
  300.      * @return string Data stored there. False if no data stored there.
  301.      */
  302.     public abstract function get($index);
  303.     
  304.     /**
  305.      * Sets the cache entry with the given index to contain the given data
  306.      *
  307.      * @param int $index Cache index
  308.      * @param string $data Data to store there
  309.      */
  310.     public abstract function set($index$data);
  311. }
  312.  
  313. /**
  314.  * Implements a cache stored in an index flat file (csv format)
  315.  *
  316.  * @author Justin DeMaris
  317.  * @package FTCache
  318.  */
  319.     /**
  320.      * The data we are storing in memory.
  321.      *
  322.      * @var file 
  323.      */
  324.     private $filename = NULL;
  325.     
  326.     /**
  327.      * Instantiate, set the size, and open the file for editting
  328.      *
  329.      * @param int $size Size of the cache in rows
  330.      * @param string $filename 
  331.      */
  332.     public function __construct($size$filename 'cache'$clear = true{
  333.         parent::__construct($size);
  334.         $mode $clear || !file_exists($filename'w+' 'r+';
  335.         $result @fopen($filename$mode);
  336.         if $result === false )
  337.             throw new Exception('Can not open file "'.$filename.'"');
  338.         fclose($result);
  339.         $this->filename $filename;
  340.     }
  341.     
  342.     /**
  343.      * Parses a CSV file for data using the given handle for input.
  344.      *
  345.      * @param file $handle 
  346.      */
  347.     private function _parse_csv($handle{
  348.         $data = array();
  349.         while $row fgetcsv($handle) ) {
  350.             // Make sure data is properly formatted
  351.             if !isset($row[0]|| !isset($row[1]|| count($row< 2 )
  352.                 throw new Exception('Improperly formatted cache file');
  353.             if !is_numeric($row[0]) ) {
  354.                 exit;
  355.                 throw new Exception('Invalid Index in Cache File: '.$row[0]);
  356.             }
  357.             if $row[0< 0 || $row[0>= $this->_size )
  358.                 throw new Exception('Invalid Index in Cache File');
  359.             
  360.             // Reassemble the cache
  361.             $data[$row[0]] $row[1];
  362.         }
  363.         return $data;
  364.     }
  365.     
  366.     /**
  367.      * Retrieve the string of stored data at the given cache index
  368.      *
  369.      * @param int $index Cache index
  370.      * @return string Data stored there. False if no data stored there.
  371.      */
  372.     public function get($index{
  373.         // Make sure entry is within bounds
  374.         if $index >= $this->_size || $index < 0 return false;
  375.         
  376.         // If the file does not exist, then there is no data in cache
  377.         if !file_exists($this->filename) )
  378.             return false;
  379.         
  380.         // Open the file for reading
  381.         $handle @fopen($this->filename'r+');
  382.         
  383.         // Handle error
  384.         if $handle === false )
  385.             throw new Exception('Can not open file "'.$this->filename.'" for reading');
  386.         
  387.         // Lock the file for reading
  388.         if !flock($handleLOCK_SH) )
  389.             throw new Exception('Can not lock file "'.$this->filename.'" for reading');
  390.             
  391.         // Read all of the data
  392.         $data $this->_parse_csv($handle);
  393.         
  394.         // Unlock the file
  395.         flock($handleLOCK_UN);
  396.         
  397.         // Close the file
  398.         fclose($handle);
  399.         
  400.         // Make sure entry has been set
  401.         if !isset($data[$index]) ) return false;
  402.         
  403.         // Return value
  404.         return $data[$index];
  405.     }
  406.     
  407.     /**
  408.      * Sets the cache entry with the given index to contain the given data
  409.      *
  410.      * @param int $index Cache index
  411.      * @param string $data Data to store there
  412.      */
  413.     public function set($index$data{
  414.         // Make sure entry is within bounds
  415.         if $index >= $this->_size || $index < 0 return false;
  416.         
  417.         // Make sure data is a string
  418.         if !is_string($data&& !is_numeric($data) ) return false;
  419.         
  420.         // If the file doesn't exist, then create it
  421.         if !file_exists($this->filename) )
  422.             if !($dh @fopen($this->filename'w')) )
  423.                 throw new Exception('Can not create file "'.$this->filename.'"');
  424.             else
  425.                 fclose($dh);
  426.         
  427.         // Open cache file
  428.         $handle @fopen($this->filename'r+');
  429.         
  430.         // Handle error
  431.         if $handle === false )
  432.             throw new Exception('Can not open file "'.$this->filename.'" for reading');
  433.         
  434.         // Lock the resource
  435.         if !flock($handleLOCK_EX) )
  436.             throw new Exception('Can not lock file "'.$this->filename.'" for writing');
  437.         
  438.         // Read the data
  439.         $cache $this->_parse_csv($handle);
  440.         
  441.         // Clear the file
  442.         ftruncate($handle0);
  443.         rewind($handle);
  444.         
  445.         // Modify the data
  446.         $cache[$index$data;
  447.         
  448.         // Rewrite the data to the file
  449.         foreach $cache as $field => $content )
  450.             fputcsv($handlearray($field$content));
  451.         
  452.         // Release the lock
  453.         flock($handleLOCK_UN);
  454.             
  455.         // Close the file
  456.         fclose($handle);
  457.         
  458.         // Return success
  459.         return true;
  460.     }
  461. }
  462.  
  463. /**
  464.  * Implements a volatile cache that only exists for this runtime of the program.
  465.  *
  466.  * @author Justin DeMaris
  467.  * @package FTCache
  468.  */
  469.     /**
  470.      * The data we are storing in memory.
  471.      *
  472.      * @var array string
  473.      */
  474.     private $_cache = array();
  475.     
  476.     /**
  477.      * Retrieve the string of stored data at the given cache index
  478.      *
  479.      * @param int $index Cache index
  480.      * @return string Data stored there. False if no data stored there.
  481.      */
  482.     public function get($index{
  483.         // Make sure entry is within bounds
  484.         if $index >= $this->_size || $index < 0 return false;
  485.         
  486.         // Make sure entry has been set
  487.         if !isset($this->_cache[$index]) ) return false;
  488.         
  489.         // Return value
  490.         return $this->_cache[$index];
  491.     }
  492.     
  493.     /**
  494.      * Sets the cache entry with the given index to contain the given data
  495.      *
  496.      * @param int $index Cache index
  497.      * @param string $data Data to store there
  498.      */
  499.     public function set($index$data{
  500.         // Make sure entry is within bounds
  501.         if $index >= $this->_size || $index < 0 return false;
  502.         
  503.         // Make sure data is a string
  504.         if !is_string($data&& !is_numeric($data) ) return false;
  505.         
  506.         // Set entry
  507.         $this->_cache[$index$data;
  508.         
  509.         // Return success
  510.         return true;
  511.     }
  512. }
  513.  
  514. /**
  515.  * Implements a Direct Mapping strategy between the given object id and the cache index
  516.  * id by doing a modulus using the size of the cache.
  517.  *
  518.  * @author Justin DeMaris
  519.  * @package FTCache
  520.  */
  521.     /**
  522.      * Indexed map of the current state of the cache
  523.      *
  524.      * @var array int
  525.      */
  526.     private $_record = array();
  527.     
  528.     /**
  529.      * Convert a cache entry index into an object id
  530.      * 
  531.      * This method must return the object id of the cache entry stored at the given index.
  532.      * If there is nothing stored there, then it must return false.
  533.      *
  534.      * @param int $index Cache entry index
  535.      * @return int Object Id. False if we can't map that index or nothing is stored there.
  536.      */
  537.     public function fromCache($index{
  538.         if isset($this->_record[$index]) )
  539.             return $this->_record[$index];
  540.         else
  541.             return false;
  542.     }
  543.     
  544.     /**
  545.      * Whenever the cache will store an object in the container, it informs this strategy
  546.      * object by calling this method.
  547.      *
  548.      * @param int $id Object id
  549.      */
  550.     public function stored($id{
  551.         $index $this->toCache($id);
  552.         
  553.         // If we can't store it, then exit
  554.         if $index === false return false;
  555.         
  556.         // Otherwise store it
  557.         $this->_record[$index$id;
  558.  
  559.         // Success
  560.         return true;
  561.     }
  562.  
  563.     /**
  564.      * Convert an object ID into a cache entry index.
  565.      * 
  566.      * This method must return the index that the given object id is stored at if it is
  567.      * currently stored, or the index that it will be stored at if it is not already stored.
  568.      * 
  569.      * @param int $id Object ID
  570.      * @return int Cache Index. False if we can't / won't map that index
  571.      */
  572.     public function toCache($id{
  573.         // Make sure it is a valid id
  574.         if !is_numeric($id) ) return false;
  575.         
  576.         // Only work with positive numbers
  577.         $id abs($id);
  578.         
  579.         // Return the entry id
  580.         return $id $this->_size;
  581.     }
  582. }
  583.  
  584. ?>

Documentation generated on Sat, 01 Mar 2008 03:16:44 +0000 by phpDocumentor 1.4.0