Source for file Cache.php

Documentation is available at Cache.php

  1. <?php
  2. /*
  3.  *  $Id: Cache.php 1857 2007-06-26 22:30:23Z subzero2000 $
  4.  *
  5.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16.  *
  17.  * This software consists of voluntary contributions made by many individuals
  18.  * and is licensed under the LGPL. For more information, see
  19.  * <http://www.phpdoctrine.com>.
  20.  */
  21. Doctrine::autoload('Doctrine_EventListener');
  22. /**
  23.  * Doctrine_Cache
  24.  *
  25.  * @package     Doctrine
  26.  * @subpackage  Doctrine_Cache
  27.  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
  28.  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
  29.  * @category    Object Relational Mapping
  30.  * @link        www.phpdoctrine.com
  31.  * @since       1.0
  32.  * @version     $Revision: 1857 $
  33.  */
  34. class Doctrine_Cache extends Doctrine_EventListener implements CountableIteratorAggregate
  35. {
  36.     /**
  37.      * @var array $_options                         an array of general caching options
  38.      */
  39.     protected $_options = array('size'                  => 1000,
  40.                                 'lifeTime'              => 3600,
  41.                                 'addStatsPropability'   => 0.25,
  42.                                 'savePropability'       => 0.10,
  43.                                 'cleanPropability'      => 0.01,
  44.                                 'statsFile'             => '../data/stats.cache',
  45.                                 );
  46.     /**
  47.      * @var array $_queries                         query stack
  48.      */
  49.     protected $_queries = array();
  50.     /**
  51.      * @var Doctrine_Cache_Interface $_driver       the cache driver object
  52.      */
  53.     protected $_driver;
  54.     /**
  55.      * @var array $data                             current cache data array
  56.      */
  57.     protected $_data = array();
  58.     /**
  59.      * @var boolean $success                        the success of last operation
  60.      */
  61.     protected $_success = false;
  62.     /**
  63.      * constructor
  64.      *
  65.      * @param Doctrine_Cache_Interface|string$driver       cache driver name or a driver object
  66.      * @param array $options                                cache driver options
  67.      */
  68.     public function __construct($driver$options array())
  69.     {
  70.         if (is_object($driver)) {
  71.            if ($driver instanceof Doctrine_Cache_Interface)) {
  72.                throw new Doctrine_Cache_Exception('Driver should implement Doctrine_Cache_Interface.');
  73.            }
  74.            
  75.            $this->_driver = $driver;
  76.            $this->_driver->setOptions($options);
  77.         else {
  78.             $class 'Doctrine_Cache_' ucwords(strtolower($driver));
  79.     
  80.             if class_exists($class)) {
  81.                 throw new Doctrine_Cache_Exception('Cache driver ' $driver ' could not be found.');
  82.             }
  83.     
  84.             $this->_driver = new $class($options);
  85.         }
  86.     }
  87.     /**
  88.      * getDriver
  89.      * returns the current cache driver
  90.      *
  91.      * @return Doctrine_Cache_Driver 
  92.      */
  93.     public function getDriver()
  94.     {
  95.         return $this->_driver;
  96.     }
  97.     /**
  98.      * setOption
  99.      *
  100.      * @param mixed $option     the option name
  101.      * @param mixed $value      option value
  102.      * @return boolean          TRUE on success, FALSE on failure
  103.      */
  104.     public function setOption($option$value)
  105.     {
  106.         // sanity check (we need this since we are using isset() instead of array_key_exists())
  107.         if ($value === null{
  108.             throw new Doctrine_Cache_Exception('Null values not accepted for options.');
  109.         }
  110.  
  111.         if (isset($this->_options[$option])) {
  112.             $this->_options[$option$value;
  113.             return true;
  114.         }
  115.         return false;
  116.     }
  117.     /**
  118.      * getOption
  119.      * 
  120.      * @param mixed $option     the option name
  121.      * @return mixed            option value
  122.      */
  123.     public function getOption($option)
  124.     {
  125.         if isset($this->_options[$option])) {
  126.             throw new Doctrine_Cache_Exception('Unknown option ' $option);
  127.         }
  128.  
  129.         return $this->_options[$option];
  130.     }
  131.     /**
  132.      * add
  133.      * adds a query to internal query stack
  134.      *
  135.      * @param string|array$query           sql query string
  136.      * @param string $namespace             connection namespace
  137.      * @return void 
  138.      */
  139.     public function add($query$namespace null)
  140.     {
  141.         if (isset($namespace)) {
  142.             $this->_queries[$namespace][$query;
  143.         else {
  144.             $this->_queries[$query;
  145.         }
  146.     }
  147.     /**
  148.      * getQueries
  149.      *
  150.      * @param string $namespace     optional query namespace
  151.      * @return array                an array of sql query strings
  152.      */
  153.     public function getAll($namespace null)
  154.     {
  155.         if (isset($namespace)) {
  156.             ifisset($this->_queries[$namespace])) {
  157.                 return array();
  158.             }
  159.  
  160.             return $this->_queries[$namespace];
  161.         }
  162.         
  163.         return $this->_queries;
  164.     }
  165.     /**
  166.      * pop
  167.      *
  168.      * pops a query from the stack
  169.      * @return string 
  170.      */
  171.     public function pop()
  172.     {
  173.         return array_pop($this->_queries);
  174.     }
  175.     /**
  176.      * reset
  177.      *
  178.      * removes all queries from the query stack
  179.      * @return void 
  180.      */
  181.     public function reset()
  182.     {
  183.         $this->_queries = array();
  184.     }
  185.     /**
  186.      * count
  187.      *
  188.      * @return integer          the number of queries in the stack
  189.      */
  190.     public function count(
  191.     {
  192.         return count($this->_queries);
  193.     }
  194.     /**
  195.      * getIterator
  196.      *
  197.      * @return ArrayIterator    an iterator that iterates through the query stack
  198.      */
  199.     public function getIterator()
  200.     {
  201.         return new ArrayIterator($this->_queries);
  202.     }
  203.     /**
  204.      * @return boolean          whether or not the last cache operation was successful
  205.      */
  206.     public function isSuccessful(
  207.     {
  208.         return $this->_success;
  209.     }
  210.     /**
  211.      * save
  212.      *
  213.      * @return boolean 
  214.      */
  215.     public function clean()
  216.     {
  217.         $rand (mt_rand(mt_getrandmax());
  218.  
  219.         if ($rand <= $this->_options['cleanPropability']{
  220.             $queries $this->readStats();
  221.  
  222.             $stats   array();
  223.     
  224.             foreach ($queries as $query{
  225.                 if (isset($stats[$query])) {
  226.                     $stats[$query]++;
  227.                 else {
  228.                     $stats[$query1;
  229.                 }
  230.             }
  231.             sort($stats);
  232.     
  233.             $i $this->_options['size'];
  234.     
  235.             while ($i--{
  236.                 $element next($stats);
  237.                 $query   key($stats);
  238.  
  239.                 $hash md5($query);
  240.  
  241.                 $this->_driver->delete($hash);
  242.             }
  243.         }
  244.     }
  245.     /**
  246.      * readStats
  247.      *
  248.      * @return array 
  249.      */
  250.     public function readStats(
  251.     {
  252.         if ($this->_options['statsFile'!== false{
  253.            $content file_get_contents($this->_options['statsFile']);
  254.            
  255.            $e explode("\n"$content);
  256.            
  257.            return array_map('unserialize'$e);
  258.         }
  259.         return array();
  260.     }
  261.     /**
  262.      * appendStats
  263.      *
  264.      * adds all queries to stats file
  265.      * @return void 
  266.      */
  267.     public function appendStats()
  268.     {
  269.         if ($this->_options['statsFile'!== false{
  270.  
  271.             if file_exists($this->_options['statsFile'])) {
  272.                 throw new Doctrine_Cache_Exception("Couldn't save cache statistics. Cache statistics file doesn't exists!");
  273.             }
  274.             
  275.             $rand (mt_rand(mt_getrandmax());
  276.  
  277.             if ($rand <= $this->_options['addStatsPropability']{
  278.                 file_put_contents($this->_options['statsFile']implode("\n"array_map('serialize'$this->_queries)));
  279.             }
  280.         }
  281.     }
  282.     /**
  283.      * preQuery
  284.      * listens on the Doctrine_Event preQuery event
  285.      *
  286.      * adds the issued query to internal query stack
  287.      * and checks if cached element exists
  288.      *
  289.      * @return boolean 
  290.      */
  291.     public function preQuery(Doctrine_Event $event)
  292.     {
  293.         $query $event->getQuery();
  294.  
  295.         $data  false;
  296.         // only process SELECT statements
  297.         if (strtoupper(substr(ltrim($query)06)) == 'SELECT'{
  298.  
  299.             $this->add($query$event->getInvoker()->getName());
  300.  
  301.             $data $this->_driver->fetch(md5(serialize($query)));
  302.  
  303.             $this->success ($datatrue false;
  304.  
  305.             if $data{
  306.                 $rand (mt_rand(mt_getrandmax());
  307.  
  308.                 if ($rand $this->_options['savePropability']{
  309.                     $stmt $event->getInvoker()->getAdapter()->query($query);
  310.  
  311.                     $data $stmt->fetchAll(Doctrine::FETCH_ASSOC);
  312.  
  313.                     $this->success true;
  314.  
  315.                     $this->_driver->save(md5(serialize($query))$data);
  316.                 }
  317.             }
  318.             if ($this->success)
  319.             {
  320.                 $this->_data = $data;
  321.                 return true;
  322.             }
  323.         }
  324.         return false;
  325.     }
  326.     /**
  327.      * preFetch
  328.      * listens the preFetch event of Doctrine_Connection_Statement
  329.      *
  330.      * advances the internal pointer of cached data and returns
  331.      * the current element
  332.      *
  333.      * @return array 
  334.      */
  335.     public function preFetch(Doctrine_Event $event)
  336.     {
  337.         $ret current($this->_data);
  338.         next($this->_data);
  339.         return $ret;
  340.     }
  341.     /**
  342.      * preFetch
  343.      * listens the preFetchAll event of Doctrine_Connection_Statement
  344.      *
  345.      * returns the current cache data array
  346.      *
  347.      * @return array 
  348.      */
  349.     public function preFetchAll(Doctrine_Event $event)
  350.     {
  351.         return $this->_data;
  352.     }
  353.     /**
  354.      * preExecute
  355.      * listens the preExecute event of Doctrine_Connection_Statement
  356.      *
  357.      * adds the issued query to internal query stack
  358.      * and checks if cached element exists
  359.      *
  360.      * @return boolean 
  361.      */
  362.     public function preExecute(Doctrine_Event $event)
  363.     {
  364.         $query $event->getQuery();
  365.  
  366.         $data  false;
  367.  
  368.         // only process SELECT statements
  369.         if (strtoupper(substr(ltrim($query)06)) == 'SELECT'{
  370.  
  371.             $this->add($query$event->getInvoker()->getDbh()->getName());
  372.  
  373.             $data $this->_driver->fetch(md5(serialize(array($query$event->getParams()))));
  374.  
  375.             $this->success ($datatrue false;
  376.  
  377.             if $data{
  378.                 $rand (mt_rand(mt_getrandmax());
  379.  
  380.                 if ($rand <= $this->_options['savePropability']{
  381.  
  382.                     $stmt $event->getInvoker()->getStatement();
  383.  
  384.                     $stmt->execute($event->getParams());
  385.  
  386.                     $data $stmt->fetchAll(Doctrine::FETCH_ASSOC);
  387.  
  388.                     $this->success true;
  389.  
  390.                     $this->_driver->save(md5(serialize(array($query$event->getParams())))$data);
  391.                 }
  392.             }
  393.             if ($this->success)
  394.             {
  395.                 $this->_data = $data;
  396.                 return true;
  397.             }
  398.         }
  399.         return false;
  400.     }
  401. }