/inc/popoon/components/cache.php
PHP | 565 lines | 202 code | 78 blank | 285 comment | 49 complexity | 8dbd91c85cda395fefde4162ee675350 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
- <?php
- // +----------------------------------------------------------------------+
- // | popoon |
- // +----------------------------------------------------------------------+
- // | Copyright (c) 2001-2006 Bitflux GmbH |
- // +----------------------------------------------------------------------+
- // | Licensed under the Apache License, Version 2.0 (the "License"); |
- // | you may not use this file except in compliance with the License. |
- // | You may obtain a copy of the License at |
- // | http://www.apache.org/licenses/LICENSE-2.0 |
- // | Unless required by applicable law or agreed to in writing, software |
- // | distributed under the License is distributed on an "AS IS" BASIS, |
- // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
- // | implied. See the License for the specific language governing |
- // | permissions and limitations under the License. |
- // +----------------------------------------------------------------------+
- // | Author: Hannes Gassert <hannes.gassert@unifr.ch> |
- // +----------------------------------------------------------------------+
- //
- // $Id$
-
- require_once('Cache.php');
- /**
- * Component Caching for Popoon
- *
- * This classy class should mimic the caching mechanims Cocoon uses.
- * Have a look at http://bugzilla.bitflux.ch/show_bug.cgi?id=23 and the Cocoon documentation for more on this topic.
- *
- * @package popoon
- * @author Hannes Gassert <hannes.gassert@unifr.ch>
- * @version $Id
- * @TODO Take care of sending HTTP Cache headers, implement and test other containers than the filesystem, DO COMPONENTS
- */
- class componentCache{
- /**
- * "Definition" of the 'cachable' interface (methods only)
- *
- * @var array $cachableInterface Methods required by the 'cachable' interface
- * @access private
- */
- var $cachableInterface = array('generateValidity', 'checkValidity', 'generateKey');
- /**
- * Array of PEAR::Cache container objects, one entry per container.
- *
- * @var array $cacheHandlers
- * @access private
- */
- var $cacheHandlers = array();
- /**
- * Name of default PEAR::Cache container
- *
- * @var string
- * @access private
- */
- var $defaultContainer = 'file';
-
- /**
- * Name of PEAR::Cache container currently in use (currently a container is chosen per pipeline)
- *
- * @var string
- * @access private
- */
- var $currentContainer = 'file';
-
- /**
- * Filename suffix for cache-metafiles (for validityObjects)
- *
- * @var string
- * @access private
- */
- var $metafileSuffix = '.meta';
- /**
- * Filename suffix for cache-globalfiles (for globals)
- *
- * @var string
- * @access private
- */
- var $globalSuffix = '.global';
-
- /**
- * Cache-key generated by the last component in the pipeline
- *
- * @var string MD5 hash, probably
- * @access private
- */
- var $lastCacheKey = null;
- /**
- * Indicates a cache miss.
- *
- * All components must recalculate their results if this is set to true.
- *
- * @var $miss
- * @access private
- * @_load()
- */
- var $miss = false;
-
- /**
- * Type of the last component in the pipeline
- *
- * Used by loadLast(), updated by init().
- *
- * @see init(), loadLast()
- * @access private
- */
- var $componentTypes = array();
-
- /**
- * Indicates wether the current component is cachable at all.
- * True by default, is set to false if the component doesn't implement the
- * 'cachable' interface, for example.
- *
- * @var bool
- */
- var $iscachable = true;
-
- /**
- * Cache-key generated by the current component
- *
- * @var string
- * @see $currentComponent
- * @access private
- */
- var $currentCacheKey = null;
-
- var $currentMetaObject = array();
- /**
- * Constructor
- *
- * If necessary creates a new PEAR::Cache object of the desired type and adds it to $cacheHandlers
- * Also sets $currentContainer to the name of the currenct container. Currenty you should only use the default
- * container (Container/file.php), I haven't tested anything else yet.
- *
- * @param array map:pipeline attributes
- * @see $cacheHandlers
- * @access public
- */
- function componentCache($pipelineAttribs, &$sitemap){
- $this->sitemap = &$sitemap;
- //determine cache container
- if(isset($pipelineAttribs['container'])) {
- $containerType = $pipelineAttribs['container'];
- }
- else {
- $containerType = $this->defaultContainer;
- }
- if(!isset($this->cacheHandlers[$containerType]) || !is_object($this->cacheHandlers[$containerType])){
- $this->cacheHandlers[$containerType] =& new Cache($containerType, $this->getContainerOptions($containerType, $pipelineAttribs));
- }
- $this->currentContainer = $containerType;
- }
-
- /**
- * Initalizer
- *
- * This is some kind of pseudo constructor. Hands the current component to this class.
- * This means that init() _has_ to be called for every component that should be cached.
- *
- * @param object $component Component to be cached
- * @access public
- */
- function init(&$component){
-
- //only work with components implementing the 'cachable' interface
- if(!$this->implementscachable($component)) {
- $this->iscachable = false;
- popoon::raiseError("You're trying to use a non-cachable component inside of a pipeline marked cachable.\n".
- "This should be supported somehow but isn't implemented yet. Sorry.",
- POPOON_ERROR_WARNING,
- __FILE__, __LINE__, null);
- return(false);
- }
- else{
- $this->currentComponent =& $component;
- $this->lastCacheKey = $this->currentCacheKey;
- $this->componentTypes[] = $this->currentComponent->attribs['type'];
- $this->currentCacheKey = $component->generateKey($component->attribs, $this->lastCacheKey);
- $this->setCacheGroup();
- return(true);
- }
- }
-
- /**
- * Try to get cached component output
- *
- * Tries to get a validityObject for the current request. If there's one and if the component does verify it's validity,
- * the cached content is fetched and returned. Works only with Components implementing the 'cachable' interface, of course.
- * In any other case than success returns null.
- *
- * @return string cached content
- * @access private
- * @see implementscachable(), loadValidityObject(), checkValidity(), loadCachedContent()
- */
- function _load($what = 'content'){
- $component =& $this->currentComponent;
-
- if(!$this->miss && !is_null($validityObject = $this->loadValidityObject())){ //there's a candidate in the cache!
- //test if this candidate is a valid one
- if($component->checkValidity($validityObject)){
- //if so, we do really have a valid cache hit!
- if($what == 'content') {
- return($this->loadCachedContent());
- }
- else {
- return($this->currentCacheKey);
- }
- }
- }
- //in any other case
- $this->miss = true;
- return(null);
- }
- /**
- * Load content associated with last cache key
- *
- * When we come to a point in the pipeline where we need the output of the last component, we call loadLast().
- * It resets the cache object to the state it was when handling the last component, fetches the content wanted and
- * sets back the state of this object.
- *
- * @param $target reference to the variable you wan't to have the content you're looking for put into.
- */
- function loadLast(&$target){
- //get last componentType
- $pos = count($this->componentTypes) - 2; if($pos < 0) $pos = 0; $lastComponentType = $this->componentTypes[$pos];
- //backup currentCacheKey
- $t = $this->currentCacheKey;
- //last is now current
- $this->currentCacheKey = $this->lastCacheKey;
- //also set back currentCacheGroup
- $this->setCacheGroup($lastComponentType, $this->lastCacheKey);
- /*load it
- but just, if target is_null, if it's not null, we already have something in
- $this->xml. This is mostly set, when a component before also had to regenerate
- its data and can be mostly seen when we create a whole pipeline (eg. the generator
- loads xml-data, then passes that to a transformer. There is no need to read the
- content from the cache, if something like that happens. Moreover it leads to
- problems with libxslt2/libxml internal handling, as it looses some info when
- we serialize and deserialize xml-data (this has to be tested more thoroughly)
- *by chregu*
- */
-
- if (is_null($target)) {
- $target = $this->loadCachedContent();
- }
-
- //reset all
- $this->currentCacheKey = $t;
- $this->setCacheGroup();
- }
- /**
- * Load the content associated to the current cache Key
- *
- * You'd use this rather seldom, cause most of the time you'll wan't to use loadLast()
- * @param bool $printsDirectly if header and result from cache should be printed directly (needed for serializers)
- * @see loadLast, $currentCacheKey
- */
- function load($printsDirectly = false){ return($this->loadCachedContent($printsDirectly));}
- /**
- * Check if there's something in the cache for the current cache key
- *
- * Behind this hides all the validity checking between the cache system and the components,
- * so you should call this alway. Perhaps I could in some way call this from init() to have
- * one "must-do" call less..
- *
- * @return bool true if the content associated with there's a valid cache entry for $currentCacheKey.
- * @see $currentCacheKey
- */
- function isCached(){ return(!is_null($this->_load('id')));}
- /**
- * Returns the current cache key.
- *
- * Just a getter-method. I don't remember where I wanted to use this.
- *
- * @return string
- * @see $currentCacheKey
- */
- function getCacheKey(){ return($this->currentCacheKey);}
-
- /**
- * Store component generated data
- *
- * Tells the current component to produce the data and call the methods to store that content and the according validityObject.
- *
- * @param string $launchMethod Method to call to make the component generate the content
- * @param bool $printsDirectly Indicates wether $launchMethod prints the content directly (as e.g. the readers do) or wether we have to get the content from $content
- * @access public
- * @see storeContent(), $storeValidityObhect()
- */
- function store($content = null, $launchMethod = null, $param = null, $printsDirectly = false){
-
- if(!$this->iscachable) return(false); //we could also support a mix of cachable and non-cachable compos.,
-
- if($printsDirectly) {
- ob_start();
- }
- if(!is_null($launchMethod) && method_exists($this->currentComponent, $launchMethod)) {
- $retVal = $this->currentComponent->$launchMethod($param);
- }
- else { /*add error handling here?*/ }
-
- if($printsDirectly){ // if $printsDirectly, then it must be some kind of serializer, so we need the headers on output
- $this->sitemap->printHeader();
- $content = ob_get_contents();
- ob_end_flush();
- }
- // else{ popoon_sitemap::var2XMLString($content); }
- if ( strtolower(get_class($content)) == "domdocument")
- {
- $content = $content->dumpmem();
- }
-
- $done = $this->storeContent($content) && $this->storeValidityObject() ; //only store validityObject after content was saved successfully.
- if(!$done) popoon::raiseError('Could not write to cache! Is cache_dir writable? Is your component outputting?',
- POPOON_ERROR_WARNING,
- __FILE__, __LINE__, null);
- return($content);
-
- }
-
- /**
- * Checks wether a component implements the cachable interface
- *
- * Tests if all methods in $interface_methods are implemented by $component.
- *
- * @access public
- * @param object poopon component object
- * @returns bool
- * @access public
- */
- function implementscachable(&$component){
- foreach($this->cachableInterface as $mustHaveMethod)
- if(!method_exists($component, $mustHaveMethod)) return(false);
- return(true);
- }
-
- /**
- * Try to load validityObject from $cacheKey.meta
- *
- * @return object validityObject
- */
- function loadValidityObject(){
- $metafileKey = $this->currentCacheKey . $this->metafileSuffix;
- $metafileGroup = $this->currentCacheGroup;
- $this->currentValidityObject = $this->cacheHandlers[$this->currentContainer]->get($metafileKey, $metafileGroup);
-
- return($this->currentValidityObject["validityObject"] );
- }
- /**
- * Try to load globals
- *
- * @return bool
- */
- function loadGlobals(){
- if (isset($this->currentMetaObject["globals"] ) && $globals = $this->currentMetaObject["globals"] ) {
- popoon_sitemap::setGlobalOptionsAll($globals);
- }
- return(true);
- }
-
- /**
- * Try to load header
- *
- * @return bool
- */
- function loadHeader(){
- if (isset($this->currentMetaObject["header"] ) ) {
- $this->sitemap->header = array_merge($this->currentMetaObject["header"],$this->sitemap->header);
- }
- return(true);
- }
-
-
-
- /**
- * Stores validityObject in Cache
- *
- * Tinkers file and group name and calls save() method of current PEAR::Cache container.
- *
- * @return bool
- */
- function storeValidityObject(){
- $metafileKey = $this->currentCacheKey . $this->metafileSuffix;
- $metafileGroup = $this->currentCacheGroup;
- $this->currentValidityObject["validityObject"] = $this->currentComponent->generateValidity();
- return($this->cacheHandlers[$this->currentContainer]->save($metafileKey,
- $this->currentValidityObject,
- '', //hm.. perhaps we'll have to talk aboout expiration once..
- $metafileGroup
- ));
-
- }
- /**
- * Stores Globals in currentMetaObject
- *
- * @return bool
- * @see storeContent()
- */
- function storeGlobals() {
- if (is_array($globals = popoon_sitemap::getGlobalOptionsAll())) {
- $this->currentMetaObject["globals"] = $globals;
- }
- return true;
- }
- /**
- * Stores Header in currentMetaObject
- *
- * @return bool
- * @see storeContent()
- */
- function storeHeader() {
- if (isset($this->sitemap->header) && count($this->sitemap->header) > 0 ) {
- $this->currentMetaObject["header"] = $this->sitemap->header;
- }
- return true;
- }
- /**
- * Stores component data in cache
- *
- * @param $content string representation of the output of the current component
- * @return bool
- * @see storeGlobals()
- */
- function storeContent($content = '') {
- // store the globals in the currentMetaObject
- $this->storeGlobals();
- $this->storeHeader();
- if(!empty($content)){
- return($this->cacheHandlers[$this->currentContainer]->extSave($this->currentCacheKey, $content, serialize($this->currentMetaObject), null, $this->setCacheGroup()));
- }
- else { /* else we should do popoon::error, I guess..*/ }
- }
-
- /**
- * Tries to load string content belonging to the current cache key
- *
- * @param bool $printsDirectly if header and result from cache should be printed directly (needed for serializers)
- * @return string
- * @see $currentCacheKey, loadGlobals()
- */
- function loadCachedContent($printsDirectly = false){
- $this->currentMetaObject = unserialize($this->cacheHandlers[$this->currentContainer]->getUserData($this->currentCacheKey,$this->currentCacheGroup));
- $this->loadGlobals();
- $this->loadHeader();
- if ($printsDirectly) {
- $this->sitemap->printHeader();
- print $this->cacheHandlers[$this->currentContainer]->get($this->currentCacheKey, $this->currentCacheGroup);
- return true;
- } else {
- return($this->cacheHandlers[$this->currentContainer]->get($this->currentCacheKey, $this->currentCacheGroup));
- }
- }
- /**
- * Determine group of current component
- *
- * Cache objects are grouped primarily to avoid too large directories, which would mean too long a lookup time to get
- * a particular file.
- *
- * @return string Group name
- * @access private
- */
- function setCacheGroup($type = null, $cacheKey = null){
- if(is_null($type)) $type = $this->currentComponent->attribs['type'];
- if(is_null($cacheKey)) $cacheKey = $this->currentCacheKey;
- $this->currentCacheGroup = $type . $cacheKey[0];
- return($this->currentCacheGroup);
- }
-
- /**
- * Determines the option to use for container $containerType
- *
- * This is quite a bad method. It relies halfway on the bitfluxcms environment, which it shouldn't.
- * I did not know where to put popoon-specific configuration, so I added
- * my stuff to the bitfluxcms $BX_config hash.. dunno if that's good.
- * If you don't use bitfluxcms but standalone popoon, you can put container options such as
- * 'cachedir' into your sitemap.xml as attributes of a pipeline. You'd better take absolute path names
- * there, which is something I should add to the popoon documentation, if there's such a thing :)
- *
- * @return array PEAR::Cache container options
- * @param string $containerType
- * @param array $pipelineAttribs
- */
- function getContainerOptions($containerType = 'file', $pipelineAttribs = array()){
-
- // SETTINGS FOR PEAR::Cache/Container/File
- if(strtolower($containerType) == 'file'){
- if(isset($pipelineAttribs['cachedir'])) {
- $pipelineAttribs['cache_dir'] = $pipelineAttribs['cachedir']; unset($pipelineAttribs['cachedir']);
- }
- else if(isset($GLOBALS['BX_config']['componentCaching']['params']['cache_dir'])){
- $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['componentCaching']['params']['cache_dir'];
- }
- else if (isset($GLOBALS['BX_config']['popoon']['cacheParams'])) {
- $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['popoon']['cacheParams']['cache_dir'];
- $pipelineAttribs['encoding_mode'] = $GLOBALS['BX_config']['popoon']['cacheParams']['encoding_mode'];
- }
- else if (isset($GLOBALS['BX_config']['caching']['params'])) {
- $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['caching']['params']['cache_dir'];
- $pipelineAttribs['encoding_mode'] = $GLOBALS['BX_config']['caching']['params']['encoding_mode'];
- }
- else if (defined("BX_PROJECT_DIR")) {
- $pipelineAttribs['cache_dir'] = BX_PROJECT_DIR."/tmp/cache/";
- }
- else {
- $pipelineAttribs['cache_dir'] = "./tmp/cache/";
- }
- $pipelineAttribs['max_userdata_linelength'] = 0;
- //don't bother PEAR::Cache with our own attributes
- if(isset($pipelineAttribs['cachable'])) unset($pipelineAttribs['cachable']);
- //componentCaching gets a directory for itself
- $pipelineAttribs['cache_dir'] = str_replace('//', '/', $pipelineAttribs['cache_dir'] . '/components/');
- }
- else{
- //no other containers looked at yet..
- }
- return($pipelineAttribs);
- }
- }
- ?>