PageRenderTime 45ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/popoon/components/cache.php

https://github.com/chregu/fluxcms
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
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | popoon |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 2001-2006 Bitflux GmbH |
  6. // +----------------------------------------------------------------------+
  7. // | Licensed under the Apache License, Version 2.0 (the "License"); |
  8. // | you may not use this file except in compliance with the License. |
  9. // | You may obtain a copy of the License at |
  10. // | http://www.apache.org/licenses/LICENSE-2.0 |
  11. // | Unless required by applicable law or agreed to in writing, software |
  12. // | distributed under the License is distributed on an "AS IS" BASIS, |
  13. // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
  14. // | implied. See the License for the specific language governing |
  15. // | permissions and limitations under the License. |
  16. // +----------------------------------------------------------------------+
  17. // | Author: Hannes Gassert <hannes.gassert@unifr.ch> |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id$
  21. require_once('Cache.php');
  22. /**
  23. * Component Caching for Popoon
  24. *
  25. * This classy class should mimic the caching mechanims Cocoon uses.
  26. * Have a look at http://bugzilla.bitflux.ch/show_bug.cgi?id=23 and the Cocoon documentation for more on this topic.
  27. *
  28. * @package popoon
  29. * @author Hannes Gassert <hannes.gassert@unifr.ch>
  30. * @version $Id
  31. * @TODO Take care of sending HTTP Cache headers, implement and test other containers than the filesystem, DO COMPONENTS
  32. */
  33. class componentCache{
  34. /**
  35. * "Definition" of the 'cachable' interface (methods only)
  36. *
  37. * @var array $cachableInterface Methods required by the 'cachable' interface
  38. * @access private
  39. */
  40. var $cachableInterface = array('generateValidity', 'checkValidity', 'generateKey');
  41. /**
  42. * Array of PEAR::Cache container objects, one entry per container.
  43. *
  44. * @var array $cacheHandlers
  45. * @access private
  46. */
  47. var $cacheHandlers = array();
  48. /**
  49. * Name of default PEAR::Cache container
  50. *
  51. * @var string
  52. * @access private
  53. */
  54. var $defaultContainer = 'file';
  55. /**
  56. * Name of PEAR::Cache container currently in use (currently a container is chosen per pipeline)
  57. *
  58. * @var string
  59. * @access private
  60. */
  61. var $currentContainer = 'file';
  62. /**
  63. * Filename suffix for cache-metafiles (for validityObjects)
  64. *
  65. * @var string
  66. * @access private
  67. */
  68. var $metafileSuffix = '.meta';
  69. /**
  70. * Filename suffix for cache-globalfiles (for globals)
  71. *
  72. * @var string
  73. * @access private
  74. */
  75. var $globalSuffix = '.global';
  76. /**
  77. * Cache-key generated by the last component in the pipeline
  78. *
  79. * @var string MD5 hash, probably
  80. * @access private
  81. */
  82. var $lastCacheKey = null;
  83. /**
  84. * Indicates a cache miss.
  85. *
  86. * All components must recalculate their results if this is set to true.
  87. *
  88. * @var $miss
  89. * @access private
  90. * @_load()
  91. */
  92. var $miss = false;
  93. /**
  94. * Type of the last component in the pipeline
  95. *
  96. * Used by loadLast(), updated by init().
  97. *
  98. * @see init(), loadLast()
  99. * @access private
  100. */
  101. var $componentTypes = array();
  102. /**
  103. * Indicates wether the current component is cachable at all.
  104. * True by default, is set to false if the component doesn't implement the
  105. * 'cachable' interface, for example.
  106. *
  107. * @var bool
  108. */
  109. var $iscachable = true;
  110. /**
  111. * Cache-key generated by the current component
  112. *
  113. * @var string
  114. * @see $currentComponent
  115. * @access private
  116. */
  117. var $currentCacheKey = null;
  118. var $currentMetaObject = array();
  119. /**
  120. * Constructor
  121. *
  122. * If necessary creates a new PEAR::Cache object of the desired type and adds it to $cacheHandlers
  123. * Also sets $currentContainer to the name of the currenct container. Currenty you should only use the default
  124. * container (Container/file.php), I haven't tested anything else yet.
  125. *
  126. * @param array map:pipeline attributes
  127. * @see $cacheHandlers
  128. * @access public
  129. */
  130. function componentCache($pipelineAttribs, &$sitemap){
  131. $this->sitemap = &$sitemap;
  132. //determine cache container
  133. if(isset($pipelineAttribs['container'])) {
  134. $containerType = $pipelineAttribs['container'];
  135. }
  136. else {
  137. $containerType = $this->defaultContainer;
  138. }
  139. if(!isset($this->cacheHandlers[$containerType]) || !is_object($this->cacheHandlers[$containerType])){
  140. $this->cacheHandlers[$containerType] =& new Cache($containerType, $this->getContainerOptions($containerType, $pipelineAttribs));
  141. }
  142. $this->currentContainer = $containerType;
  143. }
  144. /**
  145. * Initalizer
  146. *
  147. * This is some kind of pseudo constructor. Hands the current component to this class.
  148. * This means that init() _has_ to be called for every component that should be cached.
  149. *
  150. * @param object $component Component to be cached
  151. * @access public
  152. */
  153. function init(&$component){
  154. //only work with components implementing the 'cachable' interface
  155. if(!$this->implementscachable($component)) {
  156. $this->iscachable = false;
  157. popoon::raiseError("You're trying to use a non-cachable component inside of a pipeline marked cachable.\n".
  158. "This should be supported somehow but isn't implemented yet. Sorry.",
  159. POPOON_ERROR_WARNING,
  160. __FILE__, __LINE__, null);
  161. return(false);
  162. }
  163. else{
  164. $this->currentComponent =& $component;
  165. $this->lastCacheKey = $this->currentCacheKey;
  166. $this->componentTypes[] = $this->currentComponent->attribs['type'];
  167. $this->currentCacheKey = $component->generateKey($component->attribs, $this->lastCacheKey);
  168. $this->setCacheGroup();
  169. return(true);
  170. }
  171. }
  172. /**
  173. * Try to get cached component output
  174. *
  175. * Tries to get a validityObject for the current request. If there's one and if the component does verify it's validity,
  176. * the cached content is fetched and returned. Works only with Components implementing the 'cachable' interface, of course.
  177. * In any other case than success returns null.
  178. *
  179. * @return string cached content
  180. * @access private
  181. * @see implementscachable(), loadValidityObject(), checkValidity(), loadCachedContent()
  182. */
  183. function _load($what = 'content'){
  184. $component =& $this->currentComponent;
  185. if(!$this->miss && !is_null($validityObject = $this->loadValidityObject())){ //there's a candidate in the cache!
  186. //test if this candidate is a valid one
  187. if($component->checkValidity($validityObject)){
  188. //if so, we do really have a valid cache hit!
  189. if($what == 'content') {
  190. return($this->loadCachedContent());
  191. }
  192. else {
  193. return($this->currentCacheKey);
  194. }
  195. }
  196. }
  197. //in any other case
  198. $this->miss = true;
  199. return(null);
  200. }
  201. /**
  202. * Load content associated with last cache key
  203. *
  204. * When we come to a point in the pipeline where we need the output of the last component, we call loadLast().
  205. * It resets the cache object to the state it was when handling the last component, fetches the content wanted and
  206. * sets back the state of this object.
  207. *
  208. * @param $target reference to the variable you wan't to have the content you're looking for put into.
  209. */
  210. function loadLast(&$target){
  211. //get last componentType
  212. $pos = count($this->componentTypes) - 2; if($pos < 0) $pos = 0; $lastComponentType = $this->componentTypes[$pos];
  213. //backup currentCacheKey
  214. $t = $this->currentCacheKey;
  215. //last is now current
  216. $this->currentCacheKey = $this->lastCacheKey;
  217. //also set back currentCacheGroup
  218. $this->setCacheGroup($lastComponentType, $this->lastCacheKey);
  219. /*load it
  220. but just, if target is_null, if it's not null, we already have something in
  221. $this->xml. This is mostly set, when a component before also had to regenerate
  222. its data and can be mostly seen when we create a whole pipeline (eg. the generator
  223. loads xml-data, then passes that to a transformer. There is no need to read the
  224. content from the cache, if something like that happens. Moreover it leads to
  225. problems with libxslt2/libxml internal handling, as it looses some info when
  226. we serialize and deserialize xml-data (this has to be tested more thoroughly)
  227. *by chregu*
  228. */
  229. if (is_null($target)) {
  230. $target = $this->loadCachedContent();
  231. }
  232. //reset all
  233. $this->currentCacheKey = $t;
  234. $this->setCacheGroup();
  235. }
  236. /**
  237. * Load the content associated to the current cache Key
  238. *
  239. * You'd use this rather seldom, cause most of the time you'll wan't to use loadLast()
  240. * @param bool $printsDirectly if header and result from cache should be printed directly (needed for serializers)
  241. * @see loadLast, $currentCacheKey
  242. */
  243. function load($printsDirectly = false){ return($this->loadCachedContent($printsDirectly));}
  244. /**
  245. * Check if there's something in the cache for the current cache key
  246. *
  247. * Behind this hides all the validity checking between the cache system and the components,
  248. * so you should call this alway. Perhaps I could in some way call this from init() to have
  249. * one "must-do" call less..
  250. *
  251. * @return bool true if the content associated with there's a valid cache entry for $currentCacheKey.
  252. * @see $currentCacheKey
  253. */
  254. function isCached(){ return(!is_null($this->_load('id')));}
  255. /**
  256. * Returns the current cache key.
  257. *
  258. * Just a getter-method. I don't remember where I wanted to use this.
  259. *
  260. * @return string
  261. * @see $currentCacheKey
  262. */
  263. function getCacheKey(){ return($this->currentCacheKey);}
  264. /**
  265. * Store component generated data
  266. *
  267. * Tells the current component to produce the data and call the methods to store that content and the according validityObject.
  268. *
  269. * @param string $launchMethod Method to call to make the component generate the content
  270. * @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
  271. * @access public
  272. * @see storeContent(), $storeValidityObhect()
  273. */
  274. function store($content = null, $launchMethod = null, $param = null, $printsDirectly = false){
  275. if(!$this->iscachable) return(false); //we could also support a mix of cachable and non-cachable compos.,
  276. if($printsDirectly) {
  277. ob_start();
  278. }
  279. if(!is_null($launchMethod) && method_exists($this->currentComponent, $launchMethod)) {
  280. $retVal = $this->currentComponent->$launchMethod($param);
  281. }
  282. else { /*add error handling here?*/ }
  283. if($printsDirectly){ // if $printsDirectly, then it must be some kind of serializer, so we need the headers on output
  284. $this->sitemap->printHeader();
  285. $content = ob_get_contents();
  286. ob_end_flush();
  287. }
  288. // else{ popoon_sitemap::var2XMLString($content); }
  289. if ( strtolower(get_class($content)) == "domdocument")
  290. {
  291. $content = $content->dumpmem();
  292. }
  293. $done = $this->storeContent($content) && $this->storeValidityObject() ; //only store validityObject after content was saved successfully.
  294. if(!$done) popoon::raiseError('Could not write to cache! Is cache_dir writable? Is your component outputting?',
  295. POPOON_ERROR_WARNING,
  296. __FILE__, __LINE__, null);
  297. return($content);
  298. }
  299. /**
  300. * Checks wether a component implements the cachable interface
  301. *
  302. * Tests if all methods in $interface_methods are implemented by $component.
  303. *
  304. * @access public
  305. * @param object poopon component object
  306. * @returns bool
  307. * @access public
  308. */
  309. function implementscachable(&$component){
  310. foreach($this->cachableInterface as $mustHaveMethod)
  311. if(!method_exists($component, $mustHaveMethod)) return(false);
  312. return(true);
  313. }
  314. /**
  315. * Try to load validityObject from $cacheKey.meta
  316. *
  317. * @return object validityObject
  318. */
  319. function loadValidityObject(){
  320. $metafileKey = $this->currentCacheKey . $this->metafileSuffix;
  321. $metafileGroup = $this->currentCacheGroup;
  322. $this->currentValidityObject = $this->cacheHandlers[$this->currentContainer]->get($metafileKey, $metafileGroup);
  323. return($this->currentValidityObject["validityObject"] );
  324. }
  325. /**
  326. * Try to load globals
  327. *
  328. * @return bool
  329. */
  330. function loadGlobals(){
  331. if (isset($this->currentMetaObject["globals"] ) && $globals = $this->currentMetaObject["globals"] ) {
  332. popoon_sitemap::setGlobalOptionsAll($globals);
  333. }
  334. return(true);
  335. }
  336. /**
  337. * Try to load header
  338. *
  339. * @return bool
  340. */
  341. function loadHeader(){
  342. if (isset($this->currentMetaObject["header"] ) ) {
  343. $this->sitemap->header = array_merge($this->currentMetaObject["header"],$this->sitemap->header);
  344. }
  345. return(true);
  346. }
  347. /**
  348. * Stores validityObject in Cache
  349. *
  350. * Tinkers file and group name and calls save() method of current PEAR::Cache container.
  351. *
  352. * @return bool
  353. */
  354. function storeValidityObject(){
  355. $metafileKey = $this->currentCacheKey . $this->metafileSuffix;
  356. $metafileGroup = $this->currentCacheGroup;
  357. $this->currentValidityObject["validityObject"] = $this->currentComponent->generateValidity();
  358. return($this->cacheHandlers[$this->currentContainer]->save($metafileKey,
  359. $this->currentValidityObject,
  360. '', //hm.. perhaps we'll have to talk aboout expiration once..
  361. $metafileGroup
  362. ));
  363. }
  364. /**
  365. * Stores Globals in currentMetaObject
  366. *
  367. * @return bool
  368. * @see storeContent()
  369. */
  370. function storeGlobals() {
  371. if (is_array($globals = popoon_sitemap::getGlobalOptionsAll())) {
  372. $this->currentMetaObject["globals"] = $globals;
  373. }
  374. return true;
  375. }
  376. /**
  377. * Stores Header in currentMetaObject
  378. *
  379. * @return bool
  380. * @see storeContent()
  381. */
  382. function storeHeader() {
  383. if (isset($this->sitemap->header) && count($this->sitemap->header) > 0 ) {
  384. $this->currentMetaObject["header"] = $this->sitemap->header;
  385. }
  386. return true;
  387. }
  388. /**
  389. * Stores component data in cache
  390. *
  391. * @param $content string representation of the output of the current component
  392. * @return bool
  393. * @see storeGlobals()
  394. */
  395. function storeContent($content = '') {
  396. // store the globals in the currentMetaObject
  397. $this->storeGlobals();
  398. $this->storeHeader();
  399. if(!empty($content)){
  400. return($this->cacheHandlers[$this->currentContainer]->extSave($this->currentCacheKey, $content, serialize($this->currentMetaObject), null, $this->setCacheGroup()));
  401. }
  402. else { /* else we should do popoon::error, I guess..*/ }
  403. }
  404. /**
  405. * Tries to load string content belonging to the current cache key
  406. *
  407. * @param bool $printsDirectly if header and result from cache should be printed directly (needed for serializers)
  408. * @return string
  409. * @see $currentCacheKey, loadGlobals()
  410. */
  411. function loadCachedContent($printsDirectly = false){
  412. $this->currentMetaObject = unserialize($this->cacheHandlers[$this->currentContainer]->getUserData($this->currentCacheKey,$this->currentCacheGroup));
  413. $this->loadGlobals();
  414. $this->loadHeader();
  415. if ($printsDirectly) {
  416. $this->sitemap->printHeader();
  417. print $this->cacheHandlers[$this->currentContainer]->get($this->currentCacheKey, $this->currentCacheGroup);
  418. return true;
  419. } else {
  420. return($this->cacheHandlers[$this->currentContainer]->get($this->currentCacheKey, $this->currentCacheGroup));
  421. }
  422. }
  423. /**
  424. * Determine group of current component
  425. *
  426. * Cache objects are grouped primarily to avoid too large directories, which would mean too long a lookup time to get
  427. * a particular file.
  428. *
  429. * @return string Group name
  430. * @access private
  431. */
  432. function setCacheGroup($type = null, $cacheKey = null){
  433. if(is_null($type)) $type = $this->currentComponent->attribs['type'];
  434. if(is_null($cacheKey)) $cacheKey = $this->currentCacheKey;
  435. $this->currentCacheGroup = $type . $cacheKey[0];
  436. return($this->currentCacheGroup);
  437. }
  438. /**
  439. * Determines the option to use for container $containerType
  440. *
  441. * This is quite a bad method. It relies halfway on the bitfluxcms environment, which it shouldn't.
  442. * I did not know where to put popoon-specific configuration, so I added
  443. * my stuff to the bitfluxcms $BX_config hash.. dunno if that's good.
  444. * If you don't use bitfluxcms but standalone popoon, you can put container options such as
  445. * 'cachedir' into your sitemap.xml as attributes of a pipeline. You'd better take absolute path names
  446. * there, which is something I should add to the popoon documentation, if there's such a thing :)
  447. *
  448. * @return array PEAR::Cache container options
  449. * @param string $containerType
  450. * @param array $pipelineAttribs
  451. */
  452. function getContainerOptions($containerType = 'file', $pipelineAttribs = array()){
  453. // SETTINGS FOR PEAR::Cache/Container/File
  454. if(strtolower($containerType) == 'file'){
  455. if(isset($pipelineAttribs['cachedir'])) {
  456. $pipelineAttribs['cache_dir'] = $pipelineAttribs['cachedir']; unset($pipelineAttribs['cachedir']);
  457. }
  458. else if(isset($GLOBALS['BX_config']['componentCaching']['params']['cache_dir'])){
  459. $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['componentCaching']['params']['cache_dir'];
  460. }
  461. else if (isset($GLOBALS['BX_config']['popoon']['cacheParams'])) {
  462. $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['popoon']['cacheParams']['cache_dir'];
  463. $pipelineAttribs['encoding_mode'] = $GLOBALS['BX_config']['popoon']['cacheParams']['encoding_mode'];
  464. }
  465. else if (isset($GLOBALS['BX_config']['caching']['params'])) {
  466. $pipelineAttribs['cache_dir'] = $GLOBALS['BX_config']['caching']['params']['cache_dir'];
  467. $pipelineAttribs['encoding_mode'] = $GLOBALS['BX_config']['caching']['params']['encoding_mode'];
  468. }
  469. else if (defined("BX_PROJECT_DIR")) {
  470. $pipelineAttribs['cache_dir'] = BX_PROJECT_DIR."/tmp/cache/";
  471. }
  472. else {
  473. $pipelineAttribs['cache_dir'] = "./tmp/cache/";
  474. }
  475. $pipelineAttribs['max_userdata_linelength'] = 0;
  476. //don't bother PEAR::Cache with our own attributes
  477. if(isset($pipelineAttribs['cachable'])) unset($pipelineAttribs['cachable']);
  478. //componentCaching gets a directory for itself
  479. $pipelineAttribs['cache_dir'] = str_replace('//', '/', $pipelineAttribs['cache_dir'] . '/components/');
  480. }
  481. else{
  482. //no other containers looked at yet..
  483. }
  484. return($pipelineAttribs);
  485. }
  486. }
  487. ?>