├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── example ├── basic.php ├── cacheariumprobe.png └── debugprobe.php ├── phpunit.xml ├── src ├── Backend │ ├── CacheAPC.php │ ├── CacheFilesystem.php │ ├── CacheMemcached.php │ ├── CacheNull.php │ ├── CacheRAM.php │ └── external │ │ ├── Lite.php │ │ └── Timed.php ├── Cache.php ├── CacheAbstract.php ├── CacheData.php ├── CacheKey.php ├── CacheLogEnum.php ├── Cached.php ├── CachedObject.php └── Exceptions │ ├── CacheInvalidBackendException.php │ ├── CacheInvalidDataException.php │ ├── CacheInvalidParameterException.php │ ├── CacheKeyClashException.php │ ├── CacheStoreFailure.php │ ├── CacheUnsupportedOperation.php │ └── NotCachedException.php └── test ├── CacheBasicTest.php ├── CacheCallbackTest.php ├── CacheDataTest.php ├── CacheInterfaceTest.php ├── CacheKeyTest.php ├── CacheLogEnumTest.php ├── CacheMemcachedTest.php ├── CacheNullTest.php ├── CacheRamTest.php ├── CacheStartEndTest.php └── CacheTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.phar 2 | vendor/ 3 | nbproject/ 4 | .settings/ 5 | .buildpath 6 | .project 7 | composer.lock 8 | /html/ 9 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [test/*, example/*] 3 | 4 | checks: 5 | php: 6 | code_rating: true 7 | duplication: true 8 | tools: 9 | php_cs_fixer: true 10 | php_code_sniffer: true 11 | php_cpd: true 12 | php_hhvm: true 13 | php_mess_detector: true 14 | php_analyzer: true 15 | php_pdepend: true 16 | sensiolabs_security_checker: true 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.4 4 | - 5.5 5 | - 5.6 6 | 7 | before_script: 8 | - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 9 | - composer self-update || true 10 | - composer --prefer-source --dev install 11 | 12 | script: 13 | - vendor/bin/phpunit --debug --coverage-clover=coverage.clover 14 | 15 | sudo: false 16 | 17 | services: 18 | - memcached 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Corollarium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Corollarium/cachearium.svg)](https://travis-ci.org/Corollarium/cachearium) 2 | [![Code Coverage](https://scrutinizer-ci.com/g/Corollarium/cachearium/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Corollarium/cachearium/?branch=master) 3 | [![Latest Stable Version](https://poser.pugx.org/corollarium/cachearium/v/stable.svg)](https://packagist.org/packages/corollarium/cachearium) 4 | [![Latest Unstable Version](https://poser.pugx.org/corollarium/cachearium/v/unstable.svg)](https://packagist.org/packages/corollarium/cachearium) 5 | [![License](https://poser.pugx.org/corollarium/cachearium/license.svg)](https://packagist.org/packages/corollarium/cachearium) 6 | 7 | # Cachearium 8 | 9 | High level cache in your PHP applications. What, another one? Nope, this one is better. Fast, simple and with easy invalidation. Includes: 10 | 11 | - recursive cache system, all the nested russian dolls you ever wanted 12 | - easy to integrate with your [existing classes](#cache-associated-with-an-object-model-that-you-can-easily-clean) 13 | - key based cache [expiration](https://signalvnoise.com/posts/3113), no more headaches to invalidate stuff 14 | - [multiple dependencies](#store-a-value-with-multiple-dependencies) 15 | - lifetime expiration, because stuff rots 16 | - [low level cache](#store-a-single-value-and-invalidate-it) storage access, when you want to go raw 17 | - lots of [examples](https://github.com/Corollarium/cachearium/tree/master/example) and [tests](https://github.com/Corollarium/cachearium/tree/master/test) ready to copy/paste 18 | - [variable fragments](#cache-with-a-variable-fragment) for things that are almost the same but not quite 19 | - [pluggable backend modules](#backends): RAM, Memcached, Filesystem and you can add your own 20 | - [detailed logs](#to-see-a-detailed-log) for profiling and debugging, and also see what is cached [live in your webpage](#live-cache-probes) 21 | 22 | Cachearium was developed by [Corollarium](https://corollarium.com) because we needed a great cache system. 23 | 24 | # Installation 25 | 26 | ## Composer 27 | 28 | Add this to your composer.json: [see packagist](https://packagist.org/packages/corollarium/cachearium) 29 | 30 | If you prefer the cutting edge version, with only the freshest bugs: 31 | 32 | ``` 33 | "corollarium/cachearium": "dev-master" 34 | ``` 35 | 36 | ## Manual 37 | 38 | No composer? No fret! 39 | 40 | - Download the package 41 | - Include `require_once('cachearium/Cached.php');` 42 | 43 | # Debug and profile 44 | 45 | ## Live cache probes 46 | 47 | ![Cachearium cache debug probes](https://raw.githubusercontent.com/Corollarium/cachearium/master/example/cacheariumprobe.png) 48 | 49 | Image showing cache debug probes. Pink areas are not cached. Green areas are cached. Note that they are nested. The red squares show the dialog with information about each cache hit/miss so you can easily see the cache key, which backend was used and other relevant information. 50 | 51 | Probes are only available when you call start()/end(). 52 | 53 | ```php 54 | 55 | $cache::$debugOnPage = true; 56 | 57 | ... 58 | if (!$cache->start($key)) { 59 | // some stuff 60 | $cache->end(); 61 | } 62 | ... 63 | 64 | // this is required for the probes 65 | $cache->footerDebug(); 66 | 67 | ``` 68 | 69 | ## To see a detailed log 70 | 71 | ```php 72 | 73 | $cache->setLog(true); 74 | .... 75 | $cache->report(); // will print a detailed report 76 | ``` 77 | 78 | # Use cases/examples 79 | 80 | See the example/ directory, because it's all there for you. Really, just point a webserver 81 | there and play. 82 | 83 | ## Store a single value and invalidate it 84 | 85 | This is basic storage. 86 | 87 | ```php 88 | $data = 'xxxx'; 89 | 90 | // store 91 | $cache = CacheAbstract::factory('your backend'); 92 | $cache->store($data, new CacheKey('Namespace', 'Subname')); 93 | 94 | // get it later 95 | try { 96 | $data2 = $cache->get(new CacheKey('Namespace', 'Subname')); 97 | // $data2 == 'xxxx'; 98 | } 99 | catch (NotCachedException($e)) { 100 | // handle not cached 101 | } 102 | 103 | // store new value with automatic invalidation 104 | $data = 'yyy'; 105 | $cache->store($data, new CacheKey('Namespace', 'Subname')); 106 | ``` 107 | 108 | ## Store using CacheData 109 | 110 | CacheData provides a more sophisticated class to store values. 111 | 112 | 113 | ```php 114 | $data = 'xxxx'; 115 | 116 | // store 117 | $cache = CacheAbstract::factory('your backend'); 118 | $cache->storeData(new CacheData(new CacheKey('Namespace', 'Subname'), $data)); 119 | 120 | // get it later 121 | try { 122 | $data2 = $cache->getData(new CacheKey('Namespace', 'Subname')); 123 | // $data2->getFirstData() == 'xxxxx' 124 | } 125 | catch (NotCachedException($e)) { 126 | // handle not cached 127 | } 128 | 129 | // store new value with automatic invalidation 130 | $lifeTime = 3000; 131 | $fancyData = 'someData'; 132 | $cache->storeData(new CacheData(new CacheKey('Namespace', 'Subname'), $fancyData), $lifeTime); 133 | ``` 134 | 135 | ## Store a value with multiple dependencies 136 | 137 | You can have multiple dependencies so you can invalidate all cache data that 138 | relate to a certain key. 139 | 140 | ```php 141 | $cache = CacheAbstract::factory('your backend'); 142 | 143 | // create a storage key and bucket 144 | $key = new CacheKey('Namespace', 'Subname'); 145 | $cd = new CacheData($key, $data); 146 | 147 | // add dependencies. setDependencies will generate immediately, avoiding races. 148 | // otherwise you find results, the DB changes in another process and you get a 149 | // stale dependency. note that addDependencies does not do that, leaving the 150 | // hash to be calculated later 151 | $dep = new CacheKey('Namespace', 'SomeDep'); 152 | $cd->setDependencies([$dep]); 153 | 154 | // store. 155 | $data = 'xxxx'; 156 | $cache->storeData($cd); 157 | 158 | // at this point $cache->get($key) will return your data 159 | 160 | // invalidate a dependency. This will be called on your write method. 161 | $cache->invalidate($dep); 162 | 163 | // at this point $cache->get($key) will throw an NotCachedException 164 | ``` 165 | 166 | ### Example: Store searches and invalidate them when an attribute is written to 167 | 168 | ```php 169 | function someSearch() { 170 | $key = new CacheKey('search', 'someSearch'); // keys for this data 171 | $cache = CacheAbstract::factory('backend'); 172 | try { 173 | return $cache->get($key); // TODO 174 | } 175 | catch (NotCachedException($e)) { 176 | // handle not cached below 177 | } 178 | 179 | $searchdata = getSomeData(); // results of some horrible slow query 180 | 181 | // attributes that are used in this search 182 | $dependencies = [ 183 | new CacheKey('attribute', 'name'), 184 | new CacheKey('attribute', 'description') 185 | new CacheKey('attribute', 'cost') 186 | ]; 187 | 188 | // create cache data 189 | $cd = 190 | $cache->storeData( 191 | (new CacheData($key, $searchdata)) 192 | ->setDependencies($dependencies); 193 | ); 194 | 195 | return $searchdata; 196 | } 197 | 198 | function writeSomeStuff() { 199 | // changed or added some attribute value in some object 200 | 201 | $cache = CacheAbstract::factory('backend'); 202 | $cache->invalidate(new CacheKey('attribute', 'name')); // invalidates any cache that depends on this key 203 | } 204 | ``` 205 | 206 | ## Cache associated with an object/model that you can easily clean 207 | 208 | It's likely that you have a MVC application. Model classes can easily cache data 209 | 210 | ```php 211 | class Foo extends Model { 212 | use Cached; 213 | 214 | /** 215 | * Unique object id in your application (primary key) 216 | */ 217 | public function getId() { 218 | return $this->id; 219 | } 220 | 221 | public function cacheClean() { 222 | $cache = CacheAbstract::factory('backend'); 223 | $cache->clean('Foo', $this->getId()); 224 | } 225 | 226 | public function save() { 227 | // save stuff on db 228 | $this->cacheClean(); // clear any cache associated with this item 229 | } 230 | 231 | public function cacheStore($data, $key) { 232 | $cache = CacheAbstract::factory('backend'); 233 | return $cache->save($data, 'Foo', $this->getId(), $key); 234 | } 235 | 236 | public function cacheGet($key) { 237 | $cache = CacheAbstract::factory('backend'); 238 | return $cache->get('Foo', $this->getId(), $key); 239 | } 240 | } 241 | ``` 242 | 243 | ## Nested cache for contents. Useful for generating HTML made of fragments 244 | 245 | This uses the russian doll approach to bubble up any invalidations. This means that 246 | if you have a list of items and you change one of them, you only invalidate its own 247 | cache entry and the entry for the whole list. You can regenerate the list with a 248 | single DB hit. 249 | 250 | ```php 251 | 252 | $cache = CacheAbstract::factory('your backend'); 253 | 254 | $cache->start(new CacheKey('main')); 255 | 256 | $cache->start(new CacheKey('header')); 257 | $cache->end(); 258 | 259 | foreach ($somestuff as $stuff) { 260 | $stuff->render(); 261 | } 262 | 263 | $cache->start(new CacheKey('footer')); 264 | 265 | $cache->end(); 266 | $cache->end(); 267 | 268 | class Stuff { 269 | public function getCacheKey() { 270 | return new CacheKey('stuff', $this->getId()); 271 | } 272 | 273 | public function write() { 274 | write_some_stuff(); 275 | 276 | $cache = CacheAbstract::factory('your backend'); 277 | $cache->clean($this->getCacheKey()); 278 | } 279 | 280 | public function render() { 281 | $cache = CacheAbstract::factory('your backend'); 282 | $cache->start($stuff->getCacheKey()->setSub('render')); 283 | 284 | $html = '

some html here

'; 285 | 286 | // other dependency if you have it 287 | $cache->addDependency($otherstuff->getCacheKey()); 288 | 289 | $cache->end(); 290 | } 291 | } 292 | ``` 293 | 294 | ## Cache with a variable fragment 295 | This is how to handle something such as a site header, that is almost completely the 296 | same except for a small part that varies for each user. 297 | 298 | ```php 299 | 300 | function callbackTesterStart() { 301 | return rand(); 302 | } 303 | 304 | $key = new CacheKey("startcallback", 1); 305 | $cache->start($key); 306 | echo "something "; 307 | 308 | // this will never be cached, every call to start() will use the rest 309 | // of the cached data and call the callback everytime for the variable data 310 | $cache->appendCallback('callbackTesterStart'); 311 | 312 | // everything goes on as planned here 313 | echo " otherthing"; 314 | $output = $cache->end(false); 315 | ``` 316 | 317 | ## Always add something specific to the cache keys 318 | 319 | Let's say for example you have a multi-language website. Caching fragments will 320 | always need to add the language as part of the key. Cachearium provides a simple 321 | way to do this by creating a special function: 322 | 323 | ```php 324 | function application_cacheDependencies() { 325 | // can return an array or a string 326 | return [Application::getLanguage(), somethingelse()]; 327 | } 328 | ``` 329 | 330 | This will be added automatically to your keys in every call to start(). If you 331 | need to override this for a single call, use recursiveStart() instead. 332 | 333 | ## Cleaning the house 334 | 335 | You can clear the entire cache with `$cache->clear()` or `CacheAbstract::clearAll()`. 336 | 337 | # Backends 338 | 339 | ## Null 340 | 341 | Does nothing. You can use it to turn off your caches for tests without changing 342 | any code calls. 343 | 344 | ## RAM 345 | 346 | Caches in RAM, for the duration of the request only. Useful for quick caches that 347 | should not persist between requests. 348 | 349 | ## Memcache 350 | 351 | Uses Memcache as a backend, and stores data in RAM temporarily to avoid repeated 352 | requests in the same run. 353 | 354 | ## Filesystem 355 | 356 | Stores in the filesystem. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corollarium/cachearium", 3 | "description": "Cache in your PHP applications. Fast, simple and with easy invalidation.", 4 | "license": "MIT", 5 | "type": "library", 6 | "homepage": "https://github.com/Corollarium/cachearium", 7 | "keywords": ["PHP", "cache"], 8 | "minimum-stability": "dev", 9 | "authors": [ 10 | { 11 | "name": "Corollarium", 12 | "email": "email@corollarium.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.4.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "4.1.*" 20 | }, 21 | "suggest": { 22 | "ext-memcached": ">=2.1.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Cachearium\\": "src/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /example/basic.php: -------------------------------------------------------------------------------- 1 | setLog(true); 13 | 14 | ?> 15 | 16 | 17 | Cachearium debug probe 18 | 19 | 45 | 46 | 47 | start(new CacheKey("outside", 1))) { 54 | // not cached 55 | echo '
'; 56 | echo "some random bla bla" . rand(); 57 | echo '
'; 58 | $cache->end(); 59 | } 60 | } 61 | 62 | echo '

first time is not cached

'; 63 | someCachedStuff(); 64 | 65 | echo '

second time is cached

'; 66 | someCachedStuff(); 67 | 68 | echo '
'; 69 | 70 | $cache->footerDebug(); 71 | 72 | $cache->report(); 73 | 74 | ?> 75 | 76 | -------------------------------------------------------------------------------- /example/cacheariumprobe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Corollarium/cachearium/bf1bbd73cc7c85e96fdeed7e38e4216f23cebbc9/example/cacheariumprobe.png -------------------------------------------------------------------------------- /example/debugprobe.php: -------------------------------------------------------------------------------- 1 | setLog(true); 13 | 14 | ?> 15 | 16 | 17 | Cachearium debug probe 18 | 19 | 44 | 45 | 46 | start(new CacheKey("outside", 1))) { 49 | // not cached 50 | 51 | // big div 52 | echo '

Outside

'; 53 | 54 | // some smaller divs 55 | for ($i = 0; $i < 3; $i++) { 56 | if (!$cache->start(new CacheKey("medium", $i))) { 57 | echo '
'; 58 | 59 | // some even smaller divs 60 | for ($j = 0; $j < 3; $j++) { 61 | if (!$cache->start(new CacheKey("small", $j))) { 62 | echo '
'; 63 | echo "Got here $i - $j"; 64 | echo '
'; 65 | $cache->end(); 66 | } 67 | } 68 | echo '
'; 69 | $cache->end(); 70 | } 71 | } 72 | 73 | // this should be cached 74 | echo '

This below will be cached, since we saved it above

'; 75 | $cache->start(new CacheKey("medium", 0)); 76 | 77 | echo '
'; 78 | $cache->end(); 79 | } 80 | 81 | // this is required for the probes 82 | $cache->footerDebug(); 83 | 84 | $cache->report(); 85 | 86 | ?> 87 | 88 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./test/ 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | ./src/Cache.php 22 | ./src/Backend/external 23 | test/ 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Backend/CacheAPC.php: -------------------------------------------------------------------------------- 1 | enable(); 39 | } 40 | 41 | public function enable() { 42 | if (!extension_loaded('apc')) { 43 | $this->enabled = false; 44 | return false; 45 | } 46 | return parent::enable(); 47 | } 48 | 49 | private function checkValidArgs($base, $id, $sub) { 50 | if (is_array($base) || is_array($id) || !is_string($sub)) { 51 | throw new CacheInvalidDataException('Invalid get parameter'); 52 | } 53 | } 54 | 55 | public function get($base, $id, $sub = LH_DEFAULT_CACHE_ID) { 56 | // @codeCoverageIgnoreStart 57 | if (!$this->enabled) { 58 | throw new NotCachedException(); 59 | } 60 | // @codeCoverageIgnoreEnd 61 | 62 | if (!is_string($sub)) { 63 | $sub = md5(serialize($sub)); 64 | } 65 | $this->checkValidArgs($base, $id, $sub); 66 | 67 | $key = (new CacheKey($base, $id, $sub))->getHash(); 68 | $success = false; 69 | $data = apc_fetch($key, $success); 70 | if (!$success) { 71 | $this->log(CacheLogEnum::MISSED, $base, $id, $sub); 72 | throw new NotCachedException(); 73 | } 74 | return $data; 75 | } 76 | 77 | public function store($data, $base, $id, $sub = LH_DEFAULT_CACHE_ID, $lifetime = 0) { 78 | // @codeCoverageIgnoreStart 79 | if (!$this->enabled) { 80 | return false; 81 | } 82 | // @codeCoverageIgnoreEnd 83 | 84 | if (!is_string($sub)) { 85 | $sub = md5(serialize($sub)); 86 | } 87 | $this->checkValidArgs($base, $id, $sub); 88 | 89 | $key = (new CacheKey($base, $id, $sub))->getHash(); 90 | apc_store($key, $data, $lifetime); 91 | return true; 92 | } 93 | 94 | public function delete($base, $id, $sub = LH_DEFAULT_CACHE_ID) { 95 | if (!is_string($sub)) { 96 | $sub = md5(serialize($sub)); 97 | } 98 | 99 | $this->checkValidArgs($base, $id, $sub); 100 | 101 | $key = (new CacheKey($base, $id, $sub))->getHash(); 102 | apc_delete($key); 103 | return true; 104 | } 105 | 106 | public function clean($base, $id) { 107 | // TODO 108 | return true; 109 | } 110 | 111 | public function clear() { 112 | apc_clear_cache('user'); 113 | return true; 114 | } 115 | 116 | public function prefetch($data) { 117 | // nothing. 118 | } 119 | 120 | public function report() { 121 | if ($this->should_log == false) { 122 | return; 123 | } 124 | // TODO 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Backend/CacheFilesystem.php: -------------------------------------------------------------------------------- 1 | lifetime = 3600 * 24 * 30; 51 | $this->enable(); 52 | } 53 | 54 | /** 55 | * Sets path to store data 56 | * 57 | * @param string $path 58 | * @throws RuntimeException 59 | * @return CacheFS 60 | * @codeCoverageIgnore 61 | */ 62 | public function setPath($path) { 63 | if (!is_writable($this->path) || !file_exists($this->path) || !is_dir($this->path)) { 64 | throw new RuntimeException('Invalid dir or missing permissions'); 65 | } 66 | 67 | $this->path = $path . '/'; 68 | 69 | // reload 70 | if ($this->isEnabled()) { 71 | $this->enable(); 72 | } 73 | 74 | return $this; 75 | } 76 | 77 | public function enable() { 78 | $this->cache = new \Cache_Lite_Timed( 79 | array( 80 | 'cacheDir' => $this->path, 81 | 'lifeTime' => $this->getDefaultLifetime(), // in seconds 82 | 'automaticCleaningFactor' => 200, 83 | 'hashedDirectoryLevel' => 1, 84 | 'writeControl' => false, 85 | ) 86 | ); 87 | return parent::enable(); 88 | } 89 | 90 | /** 91 | * (non-PHPdoc) 92 | * @see \Cachearium\CacheAbstract::hashKey($k) 93 | */ 94 | protected function hashKey(CacheKey $k) { 95 | $group = $this->namespace . $k->base . $k->id; 96 | return $group; 97 | } 98 | 99 | /** 100 | * (non-PHPdoc) 101 | * @see \Cachearium\CacheAbstract::increment($value, $k, $default) 102 | */ 103 | public function increment($value, CacheKey $k, $default = 0) { 104 | throw new \Cachearium\Exceptions\CacheUnsupportedOperation(); 105 | } 106 | 107 | /** 108 | * (non-PHPdoc) 109 | * @see \Cachearium\CacheAbstract::get($k) 110 | */ 111 | public function get(CacheKey $k) { 112 | // @codeCoverageIgnoreStart 113 | if (!$this->enabled) { 114 | throw new \Cachearium\Exceptions\NotCachedException(); 115 | } 116 | // @codeCoverageIgnoreEnd 117 | 118 | $group = $this->hashKey($k); 119 | if (!is_string($k->sub)) { 120 | $cacheid = md5(serialize($k->sub)); 121 | } 122 | else { 123 | $cacheid = $k->sub; 124 | } 125 | $retval = $this->cache->get($cacheid, $group); 126 | 127 | $this->log( 128 | ($retval !== false ? CacheLogEnum::ACCESSED : CacheLogEnum::MISSED), 129 | $k 130 | ); 131 | 132 | if ($retval) { 133 | return unserialize($retval); 134 | } 135 | throw new \Cachearium\Exceptions\NotCachedException(); 136 | } 137 | 138 | /** 139 | * (non-PHPdoc) 140 | * @see \Cachearium\CacheAbstract::store($data, $k, $lifetime) 141 | */ 142 | public function store($data, CacheKey $k, $lifetime = -1) { 143 | // @codeCoverageIgnoreStart 144 | if (!$this->enabled) { 145 | return false; 146 | } 147 | // @codeCoverageIgnoreEnd 148 | 149 | $group = $this->hashKey($k); 150 | if (!is_string($k->sub)) { 151 | $cacheid = md5(serialize($k->sub)); 152 | } 153 | else { 154 | $cacheid = $k->sub; 155 | } 156 | return $this->cache->save( 157 | serialize($data), $cacheid, $group, ($lifetime < 0 ? $this->getDefaultLifetime() : $lifetime) 158 | ); 159 | } 160 | 161 | /** 162 | * (non-PHPdoc) 163 | * @see \Cachearium\CacheAbstract::delete($k) 164 | */ 165 | public function delete(CacheKey $k) { 166 | $group = $this->hashKey($k); 167 | if (!is_string($k->sub)) { 168 | $cacheid = md5(serialize($k->sub)); 169 | } 170 | else { 171 | $cacheid = $k->sub; 172 | } 173 | $this->log(CacheLogEnum::DELETED, $k); 174 | return $this->cache->remove($cacheid, $group); 175 | } 176 | 177 | /** 178 | * (non-PHPdoc) 179 | * @see \Cachearium\CacheAbstract::setDefaultLifetime($lifetime) 180 | */ 181 | public function setDefaultLifetime($lifetime = 0) { 182 | parent::setDefaultLifetime($lifetime); 183 | $this->cache->setLifeTime($this->getDefaultLifetime()); 184 | } 185 | 186 | /** 187 | * (non-PHPdoc) 188 | * @see \Cachearium\CacheAbstract::cleanP($base, $id) 189 | */ 190 | public function cleanP($base, $id) { 191 | // @codeCoverageIgnoreStart 192 | if (!$this->enabled) { 193 | return false; 194 | } 195 | // @codeCoverageIgnoreEnd 196 | 197 | $group = $this->hashKey(new CacheKey($base, $id)); 198 | $retval = $this->cache->clean($group, 'ingroup'); 199 | $this->log(CacheLogEnum::CLEANED, new CacheKey($base, $id)); 200 | return $retval; 201 | } 202 | 203 | /** 204 | * (non-PHPdoc) 205 | * @see \Cachearium\CacheAbstract::clear() 206 | */ 207 | public function clear() { 208 | if ($this->cache) { 209 | $this->cache->clean(); 210 | } 211 | return true; 212 | } 213 | 214 | /** 215 | * (non-PHPdoc) 216 | * @see CacheAbstract::prefetch() 217 | * @codeCoverageIgnore 218 | */ 219 | public function prefetch($data) { 220 | // nothing. 221 | } 222 | 223 | /** 224 | * (non-PHPdoc) 225 | * @see \Cachearium\CacheAbstract::report() 226 | * @codeCoverageIgnore 227 | */ 228 | public function report() { 229 | if ($this->should_log == false) { 230 | return; 231 | } 232 | echo '

Cache FS system

'; 233 | echo '

System is: ' . ($this->enabled ? 'enabled' : 'disabled') . '

'; 234 | echo '
'; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/Backend/CacheMemcached.php: -------------------------------------------------------------------------------- 1 | addServers($servers); 41 | } 42 | } 43 | 44 | return $instances; 45 | } 46 | 47 | /** 48 | * Is memcached available in this system? 49 | * 50 | * @return boolean 51 | */ 52 | static public function hasMemcachedExt() { 53 | return extension_loaded('memcached'); 54 | } 55 | 56 | // @codeCoverageIgnoreStart 57 | // Prevent users to clone the instance 58 | public function __clone() { 59 | trigger_error('Cloning is not allowed.', LH_TRIGGER_UNEXPECTED); 60 | } 61 | // @codeCoverageIgnoreEnd 62 | 63 | /** 64 | * @codeCoverageIgnore 65 | */ 66 | public function errorCallback() { 67 | // memcache error, probably offline. Logging to DB is bad (will overflow 68 | // the DB). We should really restart memcached 69 | // TODO: via Batch? 70 | $this->disable(); 71 | } 72 | 73 | public function getFetches() { 74 | return []; // TODO 75 | foreach ($data as $item) { 76 | $x = unserialize($item['keys']); 77 | if ($x === false) { 78 | continue; 79 | } 80 | 81 | parent::store($x, $item); 82 | } 83 | 84 | return $this->fetches; 85 | } 86 | 87 | /** 88 | * Constructor. 89 | * @codeCoverageIgnore 90 | */ 91 | private function __construct() { 92 | if (!self::hasMemcachedExt()) { 93 | $this->disable(); 94 | return; 95 | } 96 | $this->memcached = new \Memcached; 97 | if (!$this->memcached) { 98 | $this->disable(); 99 | return; 100 | } 101 | 102 | if (\Memcached::HAVE_IGBINARY) { 103 | $this->memcached->setOption(\Memcached::OPT_SERIALIZER, \Memcached::SERIALIZER_IGBINARY); 104 | } 105 | $this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); 106 | $this->lifetime = 3600; 107 | } 108 | 109 | /** 110 | * 111 | * @param array $servers Each entry in servers is supposed to be an array 112 | * containing hostname, port, and, optionally, weight of the server. 113 | * $servers = array( 114 | * array('mem1.domain.com', 11211, 33), 115 | * array('mem2.domain.com', 11211, 67) 116 | * ); 117 | * @return boolean 118 | * @codeCoverageIgnore 119 | */ 120 | public function addServers($servers) { 121 | if (!self::hasMemcachedExt()) { 122 | return false; 123 | } 124 | return $this->memcached->addServers($servers); 125 | } 126 | 127 | /** 128 | * Converts cachekey to a string for the data group. 129 | * 130 | * @param CacheKey $k 131 | * @return string 132 | */ 133 | private function getGroupString(CacheKey $k) { 134 | return md5(strtr($this->namespace . $k->getBase() . $k->getId(), ' ', '_')); 135 | } 136 | 137 | /** 138 | * (non-PHPdoc) 139 | * @see \Cachearium\Backend\CacheRAM::hashKey() 140 | */ 141 | protected function hashKey(CacheKey $k) { 142 | $group = $this->getGroupString($k); 143 | $ns_key = $this->memcached->get($group); 144 | 145 | // if not set, initialize it 146 | if ($ns_key == false) { 147 | $ns_key = 1; 148 | $this->memcached->set($group, $ns_key); 149 | } 150 | $group = $group . $ns_key; 151 | 152 | if (!is_string($k->sub)) { 153 | $sub = md5(serialize($k->sub)); 154 | } 155 | else { 156 | $sub = $k->sub; 157 | } 158 | $group .= $sub; 159 | 160 | return $group; 161 | } 162 | 163 | /** 164 | * (non-PHPdoc) 165 | * @see \Cachearium\Backend\CacheRAM::get() 166 | */ 167 | public function get(CacheKey $k) { 168 | // @codeCoverageIgnoreStart 169 | if (!$this->enabled) { 170 | throw new NotCachedException(); 171 | } 172 | // @codeCoverageIgnoreEnd 173 | 174 | // see if it is in RAM 175 | $should_log = $this->should_log; 176 | try { 177 | $this->should_log = false; 178 | $data = parent::get($k); 179 | $this->should_log = $should_log; 180 | $this->log(CacheLogEnum::PREFETCHED, $k); 181 | return $data; 182 | } 183 | catch (NotCachedException $e) { 184 | $this->should_log = $should_log; 185 | } 186 | 187 | $group = $this->hashKey($k); 188 | 189 | $this->fetches++; 190 | $retval = $this->memcached->get($group); 191 | 192 | $this->log( 193 | ($retval !== false ? CacheLogEnum::ACCESSED : CacheLogEnum::MISSED), 194 | $k 195 | ); 196 | if ($retval == false) { 197 | throw new NotCachedException(); 198 | } 199 | 200 | $x = unserialize($retval); 201 | if ($x === false) { 202 | throw new NotCachedException(); 203 | } 204 | 205 | parent::store($x, $k); 206 | 207 | return $x; 208 | } 209 | 210 | /** 211 | * (non-PHPdoc) 212 | * @see \Cachearium\Backend\CacheRAM::increment() 213 | */ 214 | public function increment($value, CacheKey $k, $default = 0) { 215 | // @codeCoverageIgnoreStart 216 | if (!$this->enabled) { 217 | return $default; 218 | } 219 | // @codeCoverageIgnoreEnd 220 | 221 | $group = $this->hashKey($k); 222 | 223 | $this->log(CacheLogEnum::SAVED, $k); 224 | 225 | $x = $this->memcached->increment( 226 | $group, $value, $default, $this->lifetime 227 | ); 228 | parent::store($x, $k, $this->lifetime); 229 | 230 | return $x; 231 | } 232 | 233 | /** 234 | * (non-PHPdoc) 235 | * @see \Cachearium\Backend\CacheRAM::store() 236 | */ 237 | public function store($data, CacheKey $k, $lifetime = 0) { 238 | // @codeCoverageIgnoreStart 239 | if (!$this->enabled) { 240 | return false; 241 | } 242 | // @codeCoverageIgnoreEnd 243 | 244 | $group = $this->hashKey($k); 245 | 246 | $this->log(CacheLogEnum::SAVED, $k); 247 | 248 | $x = $this->memcached->set( 249 | $group, serialize($data), $lifetime ? $lifetime : $this->lifetime 250 | ); 251 | parent::store($data, $k, $lifetime); 252 | 253 | return $x; 254 | } 255 | 256 | /** 257 | * (non-PHPdoc) 258 | * @see \Cachearium\Backend\CacheRAM::delete() 259 | */ 260 | public function delete(CacheKey $k) { 261 | // @codeCoverageIgnoreStart 262 | if (!$this->enabled) { 263 | throw new NotCachedException(); 264 | } 265 | // @codeCoverageIgnoreEnd 266 | 267 | $group = $this->hashKey($k); 268 | 269 | $this->log(CacheLogEnum::DELETED, $k); 270 | 271 | parent::delete($k); 272 | return $this->memcached->delete($group); 273 | } 274 | 275 | /** 276 | * (non-PHPdoc) 277 | * @see \Cachearium\Backend\CacheRAM::cleanP() 278 | */ 279 | public function cleanP($base, $id) { 280 | // @codeCoverageIgnoreStart 281 | if (!$this->enabled) { 282 | throw new NotCachedException(); 283 | } 284 | // @codeCoverageIgnoreEnd 285 | 286 | $group = $this->getGroupString(new CacheKey($base, $id)); 287 | 288 | parent::cleanP($base, $id); 289 | $this->memcached->increment($group); 290 | return true; 291 | } 292 | 293 | /** 294 | * (non-PHPdoc) 295 | * @see \Cachearium\Backend\CacheRAM::clear() 296 | */ 297 | public function clear() { 298 | if ($this->memcached) { 299 | $this->memcached->flush(); 300 | } 301 | parent::clear(); 302 | return true; 303 | } 304 | 305 | public function prefetchKeys($keys) { 306 | $retval = $this->memcached->get($keys); 307 | foreach ($retval as $i) { 308 | } 309 | } 310 | 311 | /** 312 | * (non-PHPdoc) 313 | * @see \Cachearium\Backend\CacheRAM::prefetch() 314 | */ 315 | public function prefetch($data) { 316 | $keys = array(); 317 | 318 | foreach ($data as &$item) { 319 | $keys[] = $this->hashKey($item); 320 | } 321 | 322 | $this->memcached->getDelayed($keys); 323 | 324 | // TODO: fetchall vs get? 325 | } 326 | 327 | /** 328 | * Clear prefetched data. This is rarely useful. 329 | */ 330 | public function prefetchClear() { 331 | parent::clear(); 332 | } 333 | 334 | /** 335 | * (non-PHPdoc) 336 | * @see \Cachearium\Backend\CacheRAM::report() 337 | * @codeCoverageIgnore 338 | */ 339 | public function report() { 340 | if ($this->should_log == false) { 341 | return; 342 | } 343 | echo ''; 351 | echo '

Cache MemCache system

'; 352 | echo '

System is: ' . ($this->enabled ? 'enabled' : 'disabled') . '

'; 353 | echo '

Total fetches: ' . ($this->fetches) . '

'; 354 | 355 | $stats = array_fill_keys(array_keys(CacheLogEnum::getNames()), 0); 356 | echo ''; 362 | 363 | echo ''; 368 | echo '
'; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/Backend/CacheNull.php: -------------------------------------------------------------------------------- 1 | disable(); 43 | } 44 | 45 | public function enable() { 46 | } 47 | 48 | public function get(CacheKey $k) { 49 | throw new NotCachedException(); 50 | } 51 | 52 | public function increment($value, CacheKey $k, $default = 0) { 53 | return $default; 54 | } 55 | 56 | public function store($data, CacheKey $k, $lifetime = 0) { 57 | return true; 58 | } 59 | 60 | public function delete(CacheKey $k) { 61 | return true; 62 | } 63 | 64 | public function cleanP($base, $id) { 65 | return true; 66 | } 67 | 68 | public function clear() { 69 | return true; 70 | } 71 | 72 | public function start(CacheKey $k, $lifetime = null, $print = true, $fail = false) { 73 | return false; 74 | } 75 | 76 | public function end($print = true) { 77 | return ''; 78 | } 79 | 80 | public function prefetch($data) { 81 | } 82 | 83 | /** 84 | * @codeCoverageIgnore 85 | */ 86 | protected function hashKey(CacheKey $k) { 87 | return $k->getBase() . $k->getId() . serialize($k->getSub()); 88 | } 89 | 90 | /** 91 | * @codeCoverageIgnore 92 | */ 93 | public function report() { 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Backend/CacheRAM.php: -------------------------------------------------------------------------------- 1 | namespace . $k->base . $k->id . serialize($k->sub); 58 | } 59 | 60 | /** 61 | * @param CacheKey $k 62 | * @throws CacheInvalidDataException 63 | * @codeCoverageIgnore 64 | */ 65 | private function checkValidArgs(CacheKey $k) { 66 | if (is_array($k->base) || is_array($k->id)) { 67 | throw new CacheInvalidDataException('Invalid get parameter'); 68 | } 69 | } 70 | 71 | /** 72 | * (non-PHPdoc) 73 | * @see \Cachearium\CacheAbstract::getData($k) 74 | */ 75 | public function getData(CacheKey $k) { 76 | $cd = CacheData::unserialize($this->get($k)); 77 | 78 | if ($cd->checkUpdateToDate($this)) { 79 | return $cd; 80 | } 81 | $this->delete($k); 82 | throw new NotCachedException(); 83 | } 84 | 85 | public function increment($value, CacheKey $k, $default = 0) { 86 | // @codeCoverageIgnoreStart 87 | if (!$this->enabled) { 88 | return $default; 89 | } 90 | // @codeCoverageIgnoreEnd 91 | 92 | if (!is_string($k->sub)) { 93 | $sub = md5(serialize($k->sub)); 94 | } 95 | else { 96 | $sub = $k->sub; 97 | } 98 | $this->checkValidArgs($k); 99 | 100 | $idx = $this->namespace . $k->base . $k->id; 101 | if (isset($this->storage[$idx]) && isset($this->storage[$idx][$sub])) { 102 | $this->storage[$idx][$sub] += $value; 103 | } 104 | else { 105 | $this->storage[$idx][$sub] = $default; 106 | } 107 | return $this->storage[$idx][$sub]; 108 | } 109 | 110 | public function get(CacheKey $k) { 111 | // @codeCoverageIgnoreStart 112 | if (!$this->enabled) { 113 | throw new NotCachedException(); 114 | } 115 | // @codeCoverageIgnoreEnd 116 | 117 | if (!is_string($k->sub)) { 118 | $sub = md5(serialize($k->sub)); 119 | } 120 | else { 121 | $sub = $k->sub; 122 | } 123 | $this->checkValidArgs($k); 124 | 125 | $idx = $this->namespace . $k->base . $k->id; 126 | if (isset($this->storage[$idx]) 127 | and array_key_exists($sub, $this->storage[$idx]) 128 | ) { 129 | $this->log(CacheLogEnum::ACCESSED, $k); 130 | return $this->storage[$idx][$sub]; 131 | } 132 | $this->log(CacheLogEnum::MISSED, $k); 133 | throw new NotCachedException(); 134 | } 135 | 136 | public function store($data, CacheKey $k, $lifetime = 0) { 137 | // @codeCoverageIgnoreStart 138 | if (!$this->enabled) { 139 | return false; 140 | } 141 | // @codeCoverageIgnoreEnd 142 | 143 | if (!is_string($k->sub)) { 144 | $sub = md5(serialize($k->sub)); 145 | } 146 | else { 147 | $sub = $k->sub; 148 | } 149 | $this->checkValidArgs($k); 150 | 151 | $this->storage[$this->namespace . $k->base . $k->id][$sub] = $data; 152 | return true; 153 | } 154 | 155 | public function delete(CacheKey $k) { 156 | if (!is_string($k->sub)) { 157 | $sub = md5(serialize($k->sub)); 158 | } 159 | else { 160 | $sub = $k->sub; 161 | } 162 | 163 | $this->checkValidArgs($k); 164 | 165 | unset($this->storage[$this->namespace . $k->base . $k->id][$sub]); 166 | return true; 167 | } 168 | 169 | public function cleanP($base, $id) { 170 | unset($this->storage[$this->namespace . $base . $id]); 171 | return true; 172 | } 173 | 174 | public function clear() { 175 | $this->storage = array(); 176 | return true; 177 | } 178 | 179 | public function getMemoryLimit($memoryLimit) { 180 | return $this->memoryLimit; 181 | } 182 | 183 | /** 184 | * 185 | * @param integer $memoryLimit 186 | * @return \Cachearium\Backend\CacheRAM 187 | */ 188 | public function setMemoryLimit($memoryLimit) { 189 | $this->memoryLimit = $memoryLimit; 190 | return $this; 191 | } 192 | 193 | /** 194 | * Clears cache if PHP memory usage is above a chosen limit 195 | * This checks the ENTIRE PHP memory usage, which may be a lot more 196 | * than what is used by this backend. 197 | * 198 | * @return boolean 199 | */ 200 | public function limitRAM() { 201 | if (memory_get_usage() > $this->memoryLimit) { 202 | $this->clear(); 203 | } 204 | return true; 205 | } 206 | 207 | /** 208 | * (non-PHPdoc) 209 | * @see CacheAbstract::prefetch() 210 | * @codeCoverageIgnore 211 | */ 212 | public function prefetch($data) { 213 | // nothing. 214 | } 215 | 216 | /** 217 | * @codeCoverageIgnore 218 | */ 219 | public function report() { 220 | if ($this->should_log == false) { 221 | return; 222 | } 223 | echo '

Cache RAM system

'; 224 | echo '

System is: ' . ($this->enabled ? 'enabled' : 'disabled') . '

'; 225 | $stats = array_fill_keys(array_keys(CacheLogEnum::getNames()), 0); 226 | echo ''; 232 | 233 | echo ''; 238 | 239 | echo '
'; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/Backend/external/Lite.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * Nota : A chinese documentation (thanks to RainX ) is 17 | * available at : 18 | * http://rainx.phpmore.com/manual/cache_lite.html 19 | * 20 | * @package Cache_Lite 21 | * @category Caching 22 | * @version $Id: Lite.php,v 1.45 2006/06/03 08:10:33 fab Exp $ 23 | * @author Fabien MARTY 24 | */ 25 | 26 | define('CACHE_LITE_ERROR_RETURN', 1); 27 | define('CACHE_LITE_ERROR_DIE', 8); 28 | 29 | class Cache_Lite 30 | { 31 | 32 | // --- Private properties --- 33 | 34 | /** 35 | * Directory where to put the cache files 36 | * (make sure to add a trailing slash) 37 | * 38 | * @var string $_cacheDir 39 | */ 40 | var $_cacheDir = '/tmp/'; 41 | 42 | /** 43 | * Enable / disable caching 44 | * 45 | * (can be very usefull for the debug of cached scripts) 46 | * 47 | * @var boolean $_caching 48 | */ 49 | var $_caching = true; 50 | 51 | /** 52 | * Cache lifetime (in seconds) 53 | * 54 | * If null, the cache is valid forever. 55 | * 56 | * @var int $_lifeTime 57 | */ 58 | var $_lifeTime = 3600; 59 | 60 | /** 61 | * Enable / disable fileLocking 62 | * 63 | * (can avoid cache corruption under bad circumstances) 64 | * 65 | * @var boolean $_fileLocking 66 | */ 67 | var $_fileLocking = true; 68 | 69 | /** 70 | * Timestamp of the last valid cache 71 | * 72 | * @var int $_refreshTime 73 | */ 74 | var $_refreshTime; 75 | 76 | /** 77 | * File name (with path) 78 | * 79 | * @var string $_file 80 | */ 81 | var $_file; 82 | 83 | /** 84 | * File name (without path) 85 | * 86 | * @var string $_fileName 87 | */ 88 | var $_fileName; 89 | 90 | /** 91 | * Enable / disable write control (the cache is read just after writing to detect corrupt entries) 92 | * 93 | * Enable write control will lightly slow the cache writing but not the cache reading 94 | * Write control can detect some corrupt cache files but maybe it's not a perfect control 95 | * 96 | * @var boolean $_writeControl 97 | */ 98 | var $_writeControl = true; 99 | 100 | /** 101 | * Enable / disable read control 102 | * 103 | * If enabled, a control key is embeded in cache file and this key is compared with the one 104 | * calculated after the reading. 105 | * 106 | * @var boolean $_writeControl 107 | */ 108 | var $_readControl = false; 109 | 110 | /** 111 | * Type of read control (only if read control is enabled) 112 | * 113 | * Available values are : 114 | * 'md5' for a md5 hash control (best but slowest) 115 | * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice) 116 | * 'strlen' for a length only test (fastest) 117 | * 118 | * @var boolean $_readControlType 119 | */ 120 | var $_readControlType = 'crc32'; 121 | 122 | /** 123 | * Pear error mode (when raiseError is called) 124 | * 125 | * (see PEAR doc) 126 | * 127 | * @see setToDebug() 128 | * @var int $_pearErrorMode 129 | */ 130 | var $_pearErrorMode = CACHE_LITE_ERROR_RETURN; 131 | 132 | /** 133 | * Current cache id 134 | * 135 | * @var string $_id 136 | */ 137 | var $_id; 138 | 139 | /** 140 | * Current cache group 141 | * 142 | * @var string $_group 143 | */ 144 | var $_group; 145 | 146 | /** 147 | * Enable / Disable "Memory Caching" 148 | * 149 | * NB : There is no lifetime for memory caching ! 150 | * 151 | * @var boolean $_memoryCaching 152 | */ 153 | var $_memoryCaching = false; 154 | 155 | /** 156 | * Enable / Disable "Only Memory Caching" 157 | * (be carefull, memory caching is "beta quality") 158 | * 159 | * @var boolean $_onlyMemoryCaching 160 | */ 161 | var $_onlyMemoryCaching = false; 162 | 163 | /** 164 | * Memory caching array 165 | * 166 | * @var array $_memoryCachingArray 167 | */ 168 | var $_memoryCachingArray = array(); 169 | 170 | /** 171 | * Memory caching counter 172 | * 173 | * @var int $memoryCachingCounter 174 | */ 175 | var $_memoryCachingCounter = 0; 176 | 177 | /** 178 | * Memory caching limit 179 | * 180 | * @var int $memoryCachingLimit 181 | */ 182 | var $_memoryCachingLimit = 1000; 183 | 184 | /** 185 | * File Name protection 186 | * 187 | * if set to true, you can use any cache id or group name 188 | * if set to false, it can be faster but cache ids and group names 189 | * will be used directly in cache file names so be carefull with 190 | * special characters... 191 | * 192 | * @var boolean $fileNameProtection 193 | */ 194 | var $_fileNameProtection = true; 195 | 196 | /** 197 | * Enable / disable automatic serialization 198 | * 199 | * it can be used to save directly datas which aren't strings 200 | * (but it's slower) 201 | * 202 | * @var boolean $_serialize 203 | */ 204 | var $_automaticSerialization = false; 205 | 206 | /** 207 | * Disable / Tune the automatic cleaning process 208 | * 209 | * The automatic cleaning process destroy too old (for the given life time) 210 | * cache files when a new cache file is written. 211 | * 0 => no automatic cache cleaning 212 | * 1 => systematic cache cleaning 213 | * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write 214 | * 215 | * @var int $_automaticCleaning 216 | */ 217 | var $_automaticCleaningFactor = 0; 218 | 219 | /** 220 | * Nested directory level 221 | * 222 | * Set the hashed directory structure level. 0 means "no hashed directory 223 | * structure", 1 means "one level of directory", 2 means "two levels"... 224 | * This option can speed up Cache_Lite only when you have many thousands of 225 | * cache file. Only specific benchs can help you to choose the perfect value 226 | * for you. Maybe, 1 or 2 is a good start. 227 | * 228 | * @var int $_hashedDirectoryLevel 229 | */ 230 | var $_hashedDirectoryLevel = 0; 231 | 232 | /** 233 | * Umask for hashed directory structure 234 | * 235 | * @var int $_hashedDirectoryUmask 236 | */ 237 | var $_hashedDirectoryUmask = 0770; 238 | 239 | /** 240 | * API break for error handling in CACHE_LITE_ERROR_RETURN mode 241 | * 242 | * In CACHE_LITE_ERROR_RETURN mode, error handling was not good because 243 | * for example save() method always returned a boolean (a PEAR_Error object 244 | * would be better in CACHE_LITE_ERROR_RETURN mode). To correct this without 245 | * breaking the API, this option (false by default) can change this handling. 246 | * 247 | * @var boolean 248 | */ 249 | var $_errorHandlingAPIBreak = false; 250 | 251 | // --- Public methods --- 252 | 253 | /** 254 | * Constructor 255 | * 256 | * $options is an assoc. Available options are : 257 | * $options = array( 258 | * 'cacheDir' => directory where to put the cache files (string), 259 | * 'caching' => enable / disable caching (boolean), 260 | * 'lifeTime' => cache lifetime in seconds (int), 261 | * 'fileLocking' => enable / disable fileLocking (boolean), 262 | * 'writeControl' => enable / disable write control (boolean), 263 | * 'readControl' => enable / disable read control (boolean), 264 | * 'readControlType' => type of read control 'crc32', 'md5', 'strlen' (string), 265 | * 'pearErrorMode' => pear error mode (when raiseError is called) (cf PEAR doc) (int), 266 | * 'memoryCaching' => enable / disable memory caching (boolean), 267 | * 'onlyMemoryCaching' => enable / disable only memory caching (boolean), 268 | * 'memoryCachingLimit' => max nbr of records to store into memory caching (int), 269 | * 'fileNameProtection' => enable / disable automatic file name protection (boolean), 270 | * 'automaticSerialization' => enable / disable automatic serialization (boolean), 271 | * 'automaticCleaningFactor' => distable / tune automatic cleaning process (int), 272 | * 'hashedDirectoryLevel' => level of the hashed directory system (int), 273 | * 'hashedDirectoryUmask' => umask for hashed directory structure (int), 274 | * 'errorHandlingAPIBreak' => API break for better error handling ? (boolean) 275 | * ); 276 | * 277 | * @param array $options options 278 | * @access public 279 | */ 280 | function __construct($options = array(NULL)) 281 | { 282 | foreach($options as $key => $value) { 283 | $this->setOption($key, $value); 284 | } 285 | } 286 | 287 | /** 288 | * Generic way to set a Cache_Lite option 289 | * 290 | * see Cache_Lite constructor for available options 291 | * 292 | * @var string $name name of the option 293 | * @var mixed $value value of the option 294 | * @access public 295 | */ 296 | function setOption($name, $value) 297 | { 298 | $availableOptions = array('errorHandlingAPIBreak', 'hashedDirectoryUmask', 'hashedDirectoryLevel', 'automaticCleaningFactor', 'automaticSerialization', 'fileNameProtection', 'memoryCaching', 'onlyMemoryCaching', 'memoryCachingLimit', 'cacheDir', 'caching', 'lifeTime', 'fileLocking', 'writeControl', 'readControl', 'readControlType', 'pearErrorMode'); 299 | if (in_array($name, $availableOptions)) { 300 | $property = '_'.$name; 301 | $this->$property = $value; 302 | } 303 | } 304 | 305 | /** 306 | * Test if a cache is available and (if yes) return it 307 | * 308 | * @param string $id cache id 309 | * @param string $group name of the cache group 310 | * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested 311 | * @return string data of the cache (else : false) 312 | * @access public 313 | */ 314 | function get($id, $group = 'default', $doNotTestCacheValidity = false) 315 | { 316 | $this->_id = $id; 317 | $this->_group = $group; 318 | $data = false; 319 | if ($this->_caching) { 320 | $this->_setRefreshTime(); 321 | $this->_setFileName($id, $group); 322 | clearstatcache(); 323 | if ($this->_memoryCaching) { 324 | if (isset($this->_memoryCachingArray[$this->_file])) { 325 | if ($this->_automaticSerialization) { 326 | return unserialize($this->_memoryCachingArray[$this->_file]); 327 | } 328 | return $this->_memoryCachingArray[$this->_file]; 329 | } 330 | if ($this->_onlyMemoryCaching) { 331 | return false; 332 | } 333 | } 334 | if (($doNotTestCacheValidity) || (is_null($this->_refreshTime))) { 335 | if (file_exists($this->_file)) { 336 | $data = $this->_read(); 337 | } 338 | } else { 339 | if ((file_exists($this->_file)) && (@filemtime($this->_file) > $this->_refreshTime)) { 340 | $data = $this->_read(); 341 | } 342 | } 343 | if (($data) and ($this->_memoryCaching)) { 344 | $this->_memoryCacheAdd($data); 345 | } 346 | if (($this->_automaticSerialization) and (is_string($data))) { 347 | $data = unserialize($data); 348 | } 349 | return $data; 350 | } 351 | return false; 352 | } 353 | 354 | /** 355 | * Save some data in a cache file 356 | * 357 | * @param string $data data to put in cache (can be another type than strings if automaticSerialization is on) 358 | * @param string $id cache id 359 | * @param string $group name of the cache group 360 | * @return boolean true if no problem (else : false or a PEAR_Error object) 361 | * @access public 362 | */ 363 | function save($data, $id = NULL, $group = 'default') 364 | { 365 | if ($this->_caching) { 366 | if ($this->_automaticSerialization) { 367 | $data = serialize($data); 368 | } 369 | if (isset($id)) { 370 | $this->_setFileName($id, $group); 371 | } 372 | if ($this->_memoryCaching) { 373 | $this->_memoryCacheAdd($data); 374 | if ($this->_onlyMemoryCaching) { 375 | return true; 376 | } 377 | } 378 | if ($this->_automaticCleaningFactor>0) { 379 | $rand = rand(1, $this->_automaticCleaningFactor); 380 | if ($rand==1) { 381 | $this->clean(false, 'old'); 382 | } 383 | } 384 | if ($this->_writeControl) { 385 | $res = $this->_writeAndControl($data); 386 | if (is_bool($res)) { 387 | if ($res) { 388 | return true; 389 | } 390 | // if $res if false, we need to invalidate the cache 391 | @touch($this->_file, time() - 2*abs($this->_lifeTime)); 392 | return false; 393 | } 394 | } else { 395 | $res = $this->_write($data); 396 | } 397 | if (is_object($res)) { 398 | // $res is a PEAR_Error object 399 | if (!($this->_errorHandlingAPIBreak)) { 400 | return false; // we return false (old API) 401 | } 402 | } 403 | return $res; 404 | } 405 | return false; 406 | } 407 | 408 | /** 409 | * Remove a cache file 410 | * 411 | * @param string $id cache id 412 | * @param string $group name of the cache group 413 | * @return boolean true if no problem 414 | * @access public 415 | */ 416 | function remove($id, $group = 'default') 417 | { 418 | $this->_setFileName($id, $group); 419 | if ($this->_memoryCaching) { 420 | if (isset($this->_memoryCachingArray[$this->_file])) { 421 | unset($this->_memoryCachingArray[$this->_file]); 422 | $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1; 423 | } 424 | if ($this->_onlyMemoryCaching) { 425 | return true; 426 | } 427 | } 428 | return $this->_unlink($this->_file); 429 | } 430 | 431 | /** 432 | * Clean the cache 433 | * 434 | * if no group is specified all cache files will be destroyed 435 | * else only cache files of the specified group will be destroyed 436 | * 437 | * @param string $group name of the cache group 438 | * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup', 439 | * 'callback_myFunction' 440 | * @return boolean true if no problem 441 | * @access public 442 | */ 443 | function clean($group = false, $mode = 'ingroup') 444 | { 445 | return $this->_cleanDir($this->_cacheDir, $group, $mode); 446 | } 447 | 448 | /** 449 | * Set to debug mode 450 | * 451 | * When an error is found, the script will stop and the message will be displayed 452 | * (in debug mode only). 453 | * 454 | * @access public 455 | */ 456 | function setToDebug() 457 | { 458 | $this->setOption('pearErrorMode', CACHE_LITE_ERROR_DIE); 459 | } 460 | 461 | /** 462 | * Set a new life time 463 | * 464 | * @param int $newLifeTime new life time (in seconds) 465 | * @access public 466 | */ 467 | function setLifeTime($newLifeTime) 468 | { 469 | $this->_lifeTime = $newLifeTime; 470 | $this->_setRefreshTime(); 471 | } 472 | 473 | /** 474 | * Save the state of the caching memory array into a cache file cache 475 | * 476 | * @param string $id cache id 477 | * @param string $group name of the cache group 478 | * @access public 479 | */ 480 | function saveMemoryCachingState($id, $group = 'default') 481 | { 482 | if ($this->_caching) { 483 | $array = array( 484 | 'counter' => $this->_memoryCachingCounter, 485 | 'array' => $this->_memoryCachingState 486 | ); 487 | $data = serialize($array); 488 | $this->save($data, $id, $group); 489 | } 490 | } 491 | 492 | /** 493 | * Load the state of the caching memory array from a given cache file cache 494 | * 495 | * @param string $id cache id 496 | * @param string $group name of the cache group 497 | * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested 498 | * @access public 499 | */ 500 | function getMemoryCachingState($id, $group = 'default', $doNotTestCacheValidity = false) 501 | { 502 | if ($this->_caching) { 503 | if ($data = $this->get($id, $group, $doNotTestCacheValidity)) { 504 | $array = unserialize($data); 505 | $this->_memoryCachingCounter = $array['counter']; 506 | $this->_memoryCachingArray = $array['array']; 507 | } 508 | } 509 | } 510 | 511 | /** 512 | * Return the cache last modification time 513 | * 514 | * BE CAREFUL : THIS METHOD IS FOR HACKING ONLY ! 515 | * 516 | * @return int last modification time 517 | */ 518 | function lastModified() 519 | { 520 | return @filemtime($this->_file); 521 | } 522 | 523 | /** 524 | * Trigger error 525 | * 526 | * @param string $msg error message 527 | * @param int $code error code 528 | * @access public 529 | */ 530 | function raiseError($msg, $code) 531 | { 532 | return new Exception($msg); 533 | } 534 | 535 | /** 536 | * Extend the life of a valid cache file 537 | * 538 | * see http://pear.php.net/bugs/bug.php?id=6681 539 | * 540 | * @access public 541 | */ 542 | function extendLife() 543 | { 544 | @touch($this->_file); 545 | } 546 | 547 | // --- Private methods --- 548 | 549 | /** 550 | * Compute & set the refresh time 551 | * 552 | * @access private 553 | */ 554 | function _setRefreshTime() 555 | { 556 | if (is_null($this->_lifeTime)) { 557 | $this->_refreshTime = null; 558 | } else { 559 | $this->_refreshTime = time() - $this->_lifeTime; 560 | } 561 | } 562 | 563 | /** 564 | * Remove a file 565 | * 566 | * @param string $file complete file path and name 567 | * @return boolean true if no problem 568 | * @access private 569 | */ 570 | function _unlink($file) 571 | { 572 | if (!@unlink($file)) { 573 | return $this->raiseError('Cache_Lite : Unable to remove cache !', -3); 574 | } 575 | return true; 576 | } 577 | 578 | /** 579 | * Recursive function for cleaning cache file in the given directory 580 | * 581 | * @param string $dir directory complete path (with a trailing slash) 582 | * @param string $group name of the cache group 583 | * @param string $mode flush cache mode : 'old', 'ingroup', 'notingroup', 584 | 'callback_myFunction' 585 | * @return boolean true if no problem 586 | * @access private 587 | */ 588 | function _cleanDir($dir, $group = false, $mode = 'ingroup') 589 | { 590 | if ($this->_fileNameProtection) { 591 | $motif = ($group) ? 'cache_'.md5($group).'_' : 'cache_'; 592 | } else { 593 | $motif = ($group) ? 'cache_'.$group.'_' : 'cache_'; 594 | } 595 | if ($this->_memoryCaching) { 596 | while (list($key, ) = each($this->_memoryCachingArray)) { 597 | if (strpos($key, $motif, 0)) { 598 | unset($this->_memoryCachingArray[$key]); 599 | $this->_memoryCachingCounter = $this->_memoryCachingCounter - 1; 600 | } 601 | } 602 | if ($this->_onlyMemoryCaching) { 603 | return true; 604 | } 605 | } 606 | if (!($dh = opendir($dir))) { 607 | return $this->raiseError('Cache_Lite : Unable to open cache directory !', -4); 608 | } 609 | $result = true; 610 | while ($file = readdir($dh)) { 611 | if (($file != '.') && ($file != '..')) { 612 | if (substr($file, 0, 6)=='cache_') { 613 | $file2 = $dir . $file; 614 | if (is_file($file2)) { 615 | switch (substr($mode, 0, 9)) { 616 | case 'old': 617 | // files older than lifeTime get deleted from cache 618 | if (!is_null($this->_lifeTime)) { 619 | if ((time() - @filemtime($file2)) > $this->_lifeTime) { 620 | $result = ($result and ($this->_unlink($file2))); 621 | } 622 | } 623 | break; 624 | case 'notingroup': 625 | if (!strpos($file2, $motif, 0)) { 626 | $result = ($result and ($this->_unlink($file2))); 627 | } 628 | break; 629 | case 'callback_': 630 | $func = substr($mode, 9, strlen($mode) - 9); 631 | if ($func($file2, $group)) { 632 | $result = ($result and ($this->_unlink($file2))); 633 | } 634 | break; 635 | case 'ingroup': 636 | default: 637 | if (strpos($file2, $motif, 0)) { 638 | $result = ($result and ($this->_unlink($file2))); 639 | } 640 | break; 641 | } 642 | } 643 | if ((is_dir($file2)) and ($this->_hashedDirectoryLevel>0)) { 644 | $result = ($result and ($this->_cleanDir($file2 . '/', $group, $mode))); 645 | } 646 | } 647 | } 648 | } 649 | return $result; 650 | } 651 | 652 | /** 653 | * Add some date in the memory caching array 654 | * 655 | * @param string $data data to cache 656 | * @access private 657 | */ 658 | function _memoryCacheAdd($data) 659 | { 660 | $this->_memoryCachingArray[$this->_file] = $data; 661 | if ($this->_memoryCachingCounter >= $this->_memoryCachingLimit) { 662 | list($key, ) = each($this->_memoryCachingArray); 663 | unset($this->_memoryCachingArray[$key]); 664 | } else { 665 | $this->_memoryCachingCounter = $this->_memoryCachingCounter + 1; 666 | } 667 | } 668 | 669 | /** 670 | * Make a file name (with path) 671 | * 672 | * @param string $id cache id 673 | * @param string $group name of the group 674 | * @access private 675 | */ 676 | function _setFileName($id, $group) 677 | { 678 | if ($this->_fileNameProtection) { 679 | $suffix = 'cache_'.md5($group).'_'.md5($id); 680 | } else { 681 | $suffix = 'cache_'.$group.'_'.$id; 682 | } 683 | $root = $this->_cacheDir; 684 | if ($this->_hashedDirectoryLevel>0) { 685 | $hash = md5($suffix); 686 | for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) { 687 | $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/'; 688 | } 689 | } 690 | $this->_fileName = $suffix; 691 | $this->_file = $root.$suffix; 692 | } 693 | 694 | /** 695 | * Read the cache file and return the content 696 | * 697 | * @return string content of the cache file (else : false or a PEAR_Error object) 698 | * @access private 699 | */ 700 | function _read() 701 | { 702 | $fp = @fopen($this->_file, "rb"); 703 | if ($this->_fileLocking) @flock($fp, LOCK_SH); 704 | if ($fp) { 705 | clearstatcache(); 706 | $length = @filesize($this->_file); 707 | // $mqr = get_magic_quotes_runtime(); 708 | // set_magic_quotes_runtime(0); 709 | if ($this->_readControl) { 710 | $hashControl = @fread($fp, 32); 711 | $length = $length - 32; 712 | } 713 | if ($length) { 714 | $data = @fread($fp, $length); 715 | } else { 716 | $data = ''; 717 | } 718 | // set_magic_quotes_runtime($mqr); 719 | if ($this->_fileLocking) @flock($fp, LOCK_UN); 720 | @fclose($fp); 721 | if ($this->_readControl) { 722 | $hashData = $this->_hash($data, $this->_readControlType); 723 | if ($hashData != $hashControl) { 724 | if (!(is_null($this->_lifeTime))) { 725 | @touch($this->_file, time() - 2*abs($this->_lifeTime)); 726 | } else { 727 | @unlink($this->_file); 728 | } 729 | return false; 730 | } 731 | } 732 | return $data; 733 | } 734 | return $this->raiseError('Cache_Lite : Unable to read cache !', -2); 735 | } 736 | 737 | /** 738 | * Write the given data in the cache file 739 | * 740 | * @param string $data data to put in cache 741 | * @return boolean true if ok (a PEAR_Error object else) 742 | * @access private 743 | */ 744 | function _write($data) 745 | { 746 | $oldmask = umask(0); 747 | if ($this->_hashedDirectoryLevel > 0) { 748 | $hash = md5($this->_fileName); 749 | $root = $this->_cacheDir; 750 | for ($i=0 ; $i<$this->_hashedDirectoryLevel ; $i++) { 751 | $root = $root . 'cache_' . substr($hash, 0, $i + 1) . '/'; 752 | if (!(@is_dir($root))) { 753 | @mkdir($root, $this->_hashedDirectoryUmask); 754 | } 755 | } 756 | } 757 | $fp = @fopen($this->_file, "wb"); 758 | if ($fp) { 759 | if ($this->_fileLocking) @flock($fp, LOCK_EX); 760 | if ($this->_readControl) { 761 | @fwrite($fp, $this->_hash($data, $this->_readControlType), 32); 762 | } 763 | $len = strlen($data); 764 | @fwrite($fp, $data, $len); 765 | if ($this->_fileLocking) @flock($fp, LOCK_UN); 766 | @fclose($fp); 767 | umask($oldmask); 768 | return true; 769 | } 770 | umask($oldmask); 771 | return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1); 772 | } 773 | 774 | /** 775 | * Write the given data in the cache file and control it just after to avoir corrupted cache entries 776 | * 777 | * @param string $data data to put in cache 778 | * @return boolean true if the test is ok (else : false or a PEAR_Error object) 779 | * @access private 780 | */ 781 | function _writeAndControl($data) 782 | { 783 | $result = $this->_write($data); 784 | if (is_object($result)) { 785 | return $result; # We return the PEAR_Error object 786 | } 787 | $dataRead = $this->_read(); 788 | if (is_object($dataRead)) { 789 | return $result; # We return the PEAR_Error object 790 | } 791 | if ((is_bool($dataRead)) && (!$dataRead)) { 792 | return false; 793 | } 794 | return ($dataRead==$data); 795 | } 796 | 797 | /** 798 | * Make a control key with the string containing datas 799 | * 800 | * @param string $data data 801 | * @param string $controlType type of control 'md5', 'crc32' or 'strlen' 802 | * @return string control key 803 | * @access private 804 | */ 805 | function _hash($data, $controlType) 806 | { 807 | switch ($controlType) { 808 | case 'md5': 809 | return md5($data); 810 | case 'crc32': 811 | return sprintf('% 32d', crc32($data)); 812 | case 'strlen': 813 | return sprintf('% 32d', strlen($data)); 814 | default: 815 | return $this->raiseError('Unknown controlType ! (available values are only \'md5\', \'crc32\', \'strlen\')', -5); 816 | } 817 | } 818 | 819 | } 820 | 821 | ?> 822 | -------------------------------------------------------------------------------- /src/Backend/external/Timed.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | 18 | require_once('Lite.php'); 19 | 20 | class Cache_Lite_Timed extends Cache_Lite 21 | { 22 | var $_bufferedLifetime; 23 | 24 | // --- Public methods ---- 25 | 26 | /** 27 | * Constructor 28 | * 29 | * $options is an assoc. To have a look at availables options, 30 | * see the constructor of the Cache_Lite class in 'Cache_Lite.php' 31 | * 32 | * @param array $options options 33 | * @access public 34 | */ 35 | function __construct($options = array(NULL)) 36 | { 37 | parent::__construct($options); 38 | } 39 | 40 | /** 41 | * Save some data in a cache file 42 | * 43 | * @param string $data data to put in cache (can be another type than strings if automaticSerialization is on) 44 | * @param string $id cache id 45 | * @param string $group name of the cache group 46 | * @param int $lifetime The time in seconds that this entry should live. Defaults to the lifetime 47 | * set by the constructor. 48 | * @return boolean true if no problem (else : false or a PEAR_Error object) 49 | * @access public 50 | */ 51 | function save($data, $id = NULL, $group = 'default', $lifetime = null) 52 | { 53 | $res = parent::save($data, $id, $group); 54 | if ($res === true) { 55 | if ($lifetime == null) { 56 | $lifetime = $this->_bufferedLifetime; 57 | } 58 | if ($lifetime == null) { 59 | $lifetime = $this->_lifeTime; 60 | } 61 | $res = $this->_setLastModified(time() + $lifetime); 62 | if (is_object($res)) { 63 | // $res is a PEAR_Error object 64 | if (!($this->_errorHandlingAPIBreak)) { 65 | return false; // we return false (old API) 66 | } 67 | } 68 | } 69 | return $res; 70 | } 71 | 72 | /** 73 | * Sets the ctime/mtime status for a file for the given time. 74 | * 75 | * @param integer $time Unix timestamp 76 | * @return boolean 77 | */ 78 | function _setLastModified($time) { 79 | if (@touch($this->_file, $time, $time) === false) { 80 | return $this->raiseError('Cache_Lite : Unable to write cache file : '.$this->_file, -1); 81 | } 82 | return true; 83 | } 84 | 85 | /** 86 | * Override refresh time function. Returns current time. 87 | * 88 | */ 89 | function _setRefreshTime() { 90 | if (is_null($this->_lifeTime)) { 91 | $this->_refreshTime = null; 92 | } else { 93 | $this->_refreshTime = time(); 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/Cache.php: -------------------------------------------------------------------------------- 1 | 0, 53 | CacheLogEnum::MISSED => 0, 54 | CacheLogEnum::DELETED => 0, 55 | CacheLogEnum::CLEANED => 0, 56 | CacheLogEnum::SAVED => 0, 57 | CacheLogEnum::PREFETCHED => 0, 58 | ); 59 | 60 | /** 61 | * Stores cache log for debugging. 62 | * @var array 63 | */ 64 | protected $cache_log = array(); 65 | 66 | /** 67 | * Is log enabled? Log can take a lot of RAM, so only turn this on when 68 | * profiling. 69 | * @var boolean $should_log 70 | */ 71 | protected $should_log = false; 72 | 73 | /** 74 | * Returns basic cache statistics. See $summary. 75 | * 76 | * @return array() 77 | */ 78 | public static function getLogSummary() { 79 | return static::$summary; 80 | } 81 | 82 | public static function resetLogSummary() { 83 | static::$summary = array( 84 | CacheLogEnum::ACCESSED => 0, 85 | CacheLogEnum::MISSED => 0, 86 | CacheLogEnum::DELETED => 0, 87 | CacheLogEnum::CLEANED => 0, 88 | CacheLogEnum::SAVED => 0, 89 | CacheLogEnum::PREFETCHED => 0, 90 | ); 91 | } 92 | 93 | /** 94 | * 95 | * @param boolean $b 96 | * @return CacheAbstract 97 | */ 98 | public function setLog($b) { 99 | $this->should_log = $b; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Returns a cache 105 | * 106 | * @param string $backend 107 | * @throws Cachearium\Exceptions\CacheInvalidBackendException 108 | * @return CacheAbstract 109 | */ 110 | public static function factory($backend) { 111 | $classname = '\Cachearium\Backend\Cache' . $backend; 112 | if (!class_exists($classname)) { 113 | throw new Exceptions\CacheInvalidBackendException("Class does not exist"); 114 | } 115 | return $classname::singleton(); 116 | } 117 | 118 | /** 119 | * Clears all cache classes. 120 | * @codeCoverageIgnore 121 | */ 122 | public static function clearAll() { 123 | $caches = [ 124 | \Cachearium\Backend\CacheRAM::singleton(), 125 | \Cachearium\Backend\CacheFilesystem::singleton(), 126 | \Cachearium\Backend\CacheMemcached::singleton(), 127 | // TODO cache apc is broken \Cachearium\Backend\CacheAPC::singleton() 128 | ]; 129 | foreach($caches as $cacheInst) { 130 | if ($cacheInst->isEnabled()) { 131 | $cacheInst->clear(); 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * Enable this cache 138 | * 139 | * @return CacheAbstract this 140 | */ 141 | final public function setEnabled($b) { 142 | if ($b) { 143 | $this->enable(); 144 | } 145 | else { 146 | $this->disable(); 147 | } 148 | return $this; 149 | } 150 | 151 | /** 152 | * Enable this cache 153 | * 154 | * @return CacheAbstract this 155 | */ 156 | public function enable() { 157 | $this->enabled = true; 158 | return $this; 159 | } 160 | 161 | /** 162 | * Disable this cache 163 | * 164 | * @return CacheAbstract 165 | */ 166 | public function disable() { 167 | $this->enabled = false; 168 | return $this; 169 | } 170 | 171 | /** 172 | * @return True if cache is enabled, working and storing/retrieving data. 173 | */ 174 | public function isEnabled() { 175 | return $this->enabled; 176 | } 177 | 178 | /** 179 | * 180 | * @param number $lifetime 0 for infinite 181 | */ 182 | public function setDefaultLifetime($lifetime = 0) { 183 | $this->lifetime = $lifetime; 184 | } 185 | 186 | public function getDefaultLifetime() { 187 | return $this->lifetime; 188 | } 189 | 190 | /** 191 | * @param string $name An optional namespace. 192 | */ 193 | public function setNamespace($name) { 194 | $this->namespace = $name; 195 | return $this; 196 | } 197 | 198 | /** 199 | * @return string 200 | */ 201 | public function getNamespace() { 202 | return $this->namespace; 203 | } 204 | 205 | /** 206 | * Get cached entry. 207 | * 208 | * @param $k 209 | * @return mixed 210 | * @throws Cachearium\Exceptions\NotCachedException 211 | */ 212 | abstract public function get(CacheKey $k); 213 | 214 | /** 215 | * Same as get(), but expanded parameters. 216 | * 217 | * @param string $base 218 | * @param string $id 219 | * @param mixed $sub 220 | * @return mixed 221 | * @throws Cachearium\Exceptions\NotCachedException 222 | * @see getK 223 | */ 224 | public function getP($base, $id, $sub = null) { 225 | return $this->get(new CacheKey($base, $id, $sub)); 226 | } 227 | 228 | /** 229 | * Same as get, but assumes data was stored with a CacheData object 230 | * and will treat it accordingly. 231 | * 232 | * @param CacheKey $k 233 | * @return CacheData 234 | * @throws Cachearium\Exceptions\NotCachedException 235 | */ 236 | public function getData(CacheKey $k) { 237 | $cd = CacheData::unserialize($this->get($k)); 238 | if ($cd->checkUpdateToDate($this)) { 239 | return $cd; 240 | } 241 | throw new Exceptions\NotCachedException(); 242 | } 243 | 244 | /** 245 | * Same as getData(), but expanded parameters. 246 | * 247 | * @see getData() 248 | * @param string $base 249 | * @param string $id 250 | * @param mixed $sub 251 | */ 252 | public function getDataP($base, $id, $sub = null) { 253 | return $this->getData(new CacheKey($base, $id, $sub)); 254 | } 255 | 256 | /** 257 | * Gets data from multiple cache keys at once 258 | * 259 | * Backends may override this to provide an efficient implementation over multiple 260 | * calls to get(). 261 | * 262 | * @param array $cacheid List of cache keys 263 | * @param callable $callback if present will be called for any \NotCachedExceptions. 264 | * Callback should have this signature: (CacheAbstract $c, CacheKey $k) 265 | * @return array:mixed array with data, using same keys as cacheid. Keys not 266 | * found in cache won't be present, but no exception will be thrown 267 | */ 268 | public function getMulti(array $cacheid, $callback = null) { 269 | $retval = []; 270 | foreach ($cacheid as $k => $c) { 271 | try { 272 | $retval[$k] = $this->get($c); 273 | } 274 | catch (Exceptions\NotCachedException $e) { 275 | // if there is a callback, call it 276 | if ($callback) { 277 | $retval[$k] = call_user_func($callback, $this, $c); 278 | } 279 | } 280 | } 281 | return $retval; 282 | } 283 | 284 | /** 285 | * Increment a variable. Backend deals with this, but in general this is atomic. 286 | * Backend must only guarantee that the increment is made, but the final value 287 | * may not be current + $value due to concurrent accesses. 288 | * 289 | * @param integer $value 290 | * @param CacheKey $k 291 | * @param integer $default If key is not in cache, this value is returned. 292 | * @return integer 293 | */ 294 | abstract public function increment($value, CacheKey $k, $default = 0); 295 | 296 | /** 297 | * Invalidates a dependency index. If the index does not exist it is created. 298 | * @param CacheKey $k 299 | */ 300 | public function invalidate(CacheKey $k) { 301 | return $this->increment(1, $k, 0); 302 | } 303 | 304 | /** 305 | * Saves data in cache. 306 | * 307 | * @param mixed $data Data to save in cache 308 | * @param CacheKey $k 309 | * @param integer $lifetime The lifetime in sceonds, although it is up to the implementation whether 310 | * it is respected or not. 311 | * @return boolean true if no problem 312 | */ 313 | abstract public function store($data, CacheKey $k, $lifetime = 0); 314 | 315 | /** 316 | * Same as store() but expanded parameters 317 | * 318 | * @param mixed $data 319 | * @param string $base 320 | * @param string $sub 321 | * @param string $id 322 | * @param number $lifetime 323 | * @return boolean true if no problem 324 | * @see store() 325 | */ 326 | public function storeP($data, $base, $id, $sub = null, $lifetime = 0) { 327 | return $this->store($data, new CacheKey($base, $id, $sub), $lifetime); 328 | } 329 | 330 | /** 331 | * Same as store() but expanded parameters 332 | * 333 | * @param CacheData $data 334 | * @param number $lifetime 335 | * @return boolean true if no problem 336 | * @see store() 337 | */ 338 | public function storeData(CacheData $data, $lifetime = 0) { 339 | return $this->store($data->updateDependenciesHash($this)->serialize(), $data->key, $lifetime); 340 | } 341 | 342 | /** 343 | * Deletes an entry from the cache 344 | * 345 | * @param CacheKey $k 346 | * @return boolean 347 | */ 348 | abstract public function delete(CacheKey $k); 349 | 350 | /** 351 | * @see delete() 352 | * @param string $base 353 | * @param string $id 354 | * @param mixed $sub 355 | */ 356 | public function deleteP($base, $id, $sub = null) { 357 | return $this->delete(new CacheKey($base, $id, $sub)); 358 | } 359 | 360 | /** 361 | * Cleans cache: all entries with a certain $base and $id in the $key 362 | * are deleted. 363 | * 364 | * @param CacheKey $k 365 | * @return boolean true if no problem 366 | */ 367 | public function clean(CacheKey $k) { 368 | return $this->cleanP($k->getBase(), $k->getId()); 369 | } 370 | 371 | /** 372 | * Cleans cache: all entries with a certain $base and $id 373 | * 374 | * @return boolean true if no problem 375 | */ 376 | abstract public function cleanP($base, $id); 377 | 378 | /** 379 | * Clears entire cache. Use sparingly. 380 | */ 381 | abstract public function clear(); 382 | 383 | /** 384 | * Prefetches data which will be used. This avoids multiple trips to the cache 385 | * server if they can be avoided. 386 | * 387 | * Backend may ignore this call and implement a noop. 388 | * 389 | * @param array $data array(0 => CacheKey, ...) 390 | */ 391 | abstract public function prefetch($data); 392 | 393 | /** 394 | * Generates a report for this backend 395 | * 396 | * @codeCoverageIgnore 397 | */ 398 | abstract public function report(); 399 | 400 | /** 401 | * Starts a cache if it doesn't exist, or outputs the data and returns true. 402 | * Calls extraSub(). 403 | * 404 | * @param CacheKey $k 405 | * @param string $lifetime The lifetime, in seconds 406 | * @param boolean $print if True echoes the data 407 | * @param boolean $fail if false throws an exception if something happens, such 408 | * as not cached 409 | * @return boolean|string True if cached 410 | * @review 411 | */ 412 | public function start(CacheKey $k, $lifetime = null, $print = true, $fail = false) { 413 | $this->extraSub($k->sub); 414 | 415 | return $this->recursiveStart($k, $lifetime, $print, $fail); 416 | } 417 | 418 | /** 419 | * @see recursiveStart() 420 | */ 421 | public function recursiveStartP($base, $id, $sub = null, $lifetime = null, $print = true, $fail = false) { 422 | return $this->recursivestart(new CacheKey($base, $id, $sub), $lifetime, $print, $fail); 423 | } 424 | 425 | /** 426 | * @see start() 427 | */ 428 | public function startP($base, $id, $sub = null, $lifetime = null, $print = true, $fail = false) { 429 | return $this->start(new CacheKey($base, $id, $sub), $lifetime, $print, $fail); 430 | } 431 | 432 | /** 433 | * start() using a callable. Same as start()/c()/end(). 434 | * 435 | * @param CacheKey $k 436 | * @param callable $c A callable. Whatever it prints will be cached. 437 | * @param array $cparams parameters for the callback, optional 438 | * @param integer $lifetime 439 | */ 440 | public function startCallback(CacheKey $k, callable $c, array $cparams = [], $lifetime = null) { 441 | $data = $this->start($k, $lifetime); 442 | if ($data === false) { 443 | call_user_func_array($c, $cparams); 444 | $data = $this->end(false); 445 | } 446 | return $data; 447 | } 448 | 449 | /** 450 | * Appends a callback to the current start()/end() cache 451 | * 452 | * Callbacks are always called at runtime, their result is never cached at 453 | * this level. You may cache it in the callback, of course. 454 | * 455 | * @param function $callback 456 | * @return boolean 457 | * @review 458 | */ 459 | public function appendCallback(callable $callback) { 460 | // @codeCoverageIgnoreStart 461 | if (!$this->enabled) { 462 | return false; 463 | } 464 | // @codeCoverageIgnoreEnd 465 | 466 | if (!$this->inloop) { 467 | return false; 468 | } 469 | 470 | $data = ob_get_contents(); 471 | ob_clean(); 472 | $this->loopdata[$this->inloop]->appendData($data); 473 | $this->loopdata[$this->inloop]->appendCallback($callback); 474 | 475 | return true; 476 | } 477 | 478 | /** 479 | * Returns a key given parameters. This is up to storage and different 480 | * values may be returned for the same parameters, as storages are likely 481 | * to use key-based cache expiration. 482 | * 483 | * @param CacheKey $k 484 | */ 485 | abstract protected function hashKey(CacheKey $k); 486 | 487 | protected function keyFromDeps(CacheKey $k, $deps) { 488 | $mainkey = $this->hashKey($k); 489 | foreach ($deps as $d) { // TODO: arrays are ugly 490 | $mainkey .= $this->hashKey($d); // TODO: one fetch for all 491 | } 492 | $mainkey = md5($mainkey); 493 | return $mainkey; 494 | } 495 | 496 | /** 497 | * Get extra sub 498 | * @param unknown $sub 499 | */ 500 | private function extraSub(&$sub) { 501 | if (!is_callable('application_cacheDependencies')) { 502 | return; 503 | } 504 | $extra = application_cacheDependencies(); 505 | if (is_array($sub)) { 506 | $sub['cacheExtraSubApplication'] = $extra; 507 | } 508 | else { 509 | $sub .= $extra; 510 | } 511 | } 512 | 513 | public function newstart(CacheKey $k, $lifetime = null, $fail = false) { 514 | // @codeCoverageIgnoreStart 515 | if (!$this->enabled) { 516 | return false; 517 | } 518 | // @codeCoverageIgnoreEnd 519 | 520 | // fetch cache 521 | try { 522 | $cachedata = $this->getData($k); 523 | } catch (Exceptions\NotCachedException $e) { 524 | // not cached 525 | if ($fail) { 526 | throw $e; 527 | } 528 | } 529 | 530 | $this->inloop++; 531 | $this->loopdata[$this->inloop] = new CacheData(); 532 | if ($this->inloop > 1) { 533 | // we are recursive. push whatever we have so far in the previous cache 534 | $data = ob_get_contents(); 535 | ob_clean(); 536 | $this->loopdata[$this->inloop - 1]->appendData($data); 537 | $this->loopdata[$this->inloop - 1]->appendRecursion($k); 538 | } 539 | else { 540 | // something was not cached below. We invalidated all cache 541 | // dependencies 542 | } 543 | 544 | $this->loopdata[$this->inloop]->setKey($k); 545 | $this->loopdata[$this->inloop]->lifetime = $lifetime ? $lifetime : $this->lifetime; 546 | 547 | ob_start(); 548 | ob_implicit_flush(false); 549 | 550 | return false; 551 | } 552 | 553 | public function newEnd($print = true) { 554 | // @codeCoverageIgnoreStart 555 | if (!$this->enabled) { 556 | return false; 557 | } 558 | // @codeCoverageIgnoreEnd 559 | 560 | $data = ob_get_clean(); 561 | 562 | /* @var $cachedata CacheData */ 563 | $cachedata = $this->loopdata[$this->inloop]; 564 | $cachedata->appendData($data); 565 | 566 | $cachedata->generateDependenciesHash($this); 567 | $mainkey = $this->keyFromDeps($cachedata->getKey(), $cachedata->dependencies); 568 | if (!$this->storeP($cachedata, 'cacherecursive', 0, $mainkey)) { 569 | throw new \Cachearium\Exceptions\CacheStoreFailure("Storing key"); 570 | } 571 | if (!$this->storeData($cachedata)) { 572 | throw new \Cachearium\Exceptions\CacheStoreFailure("Storing data"); 573 | } 574 | 575 | // if recursive 576 | $this->inloop--; 577 | if ($this->inloop > 0) { 578 | return false; 579 | } 580 | 581 | if ($print) { 582 | $key = "cache-" . rand(); 583 | // @codeCoverageIgnoreStart 584 | if (static::$debugOnPage) { 585 | echo ''; 593 | } 594 | // @codeCoverageIgnoreEnd 595 | 596 | echo $cachedata->stringify($this); 597 | 598 | // @codeCoverageIgnoreStart 599 | if (static::$debugOnPage) { 600 | echo ''; 601 | } 602 | // @codeCoverageIgnoreEnd 603 | return; 604 | } 605 | 606 | return $cachedata->stringify($this); 607 | } 608 | 609 | /** 610 | * Prints HTML for cache debug probes -> opens tag 611 | * 612 | * @param string $key 613 | * @param CacheData $cachedata 614 | * @param string $type 615 | * @codeCoverageIgnore 616 | */ 617 | protected function printProbeStart($key, CacheData $cachedata, $type) { 618 | echo ''; 626 | } 627 | 628 | /** 629 | * Prints HTML for cache debug probes -> closes tag 630 | * 631 | * @param string $key 632 | * @param CacheData $cachedata 633 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 634 | * @codeCoverageIgnore 635 | */ 636 | protected function printProbeEnd($key, CacheData $cachedata) { 637 | echo ''; 638 | } 639 | 640 | /** 641 | * 642 | * @param CacheKey $k 643 | * @param integer $lifetime if null uses the class default 644 | * @param boolean $print 645 | * @param boolean $fail if true throws a NotCachedException if not cached. 646 | * @throws Cachearium\Exceptions\NotCachedException 647 | * @throws Cachearium\Exceptions\CacheKeyClashException 648 | * @return string The cached item as a string or false if not cached. 649 | */ 650 | public function recursiveStart(CacheKey $k, $lifetime = null, $print = true, $fail = false) { 651 | // @codeCoverageIgnoreStart 652 | if (!$this->enabled) { 653 | return false; 654 | } 655 | // @codeCoverageIgnoreEnd 656 | 657 | foreach ($this->loopdata as $l) { 658 | /* @var $l CacheData */ 659 | if ($l->checkClash($k)) { 660 | throw new Exceptions\CacheKeyClashException(); 661 | } 662 | } 663 | 664 | // check if we are inside another cache for automatic dependencies. 665 | /* @var $cachedata CacheData */ 666 | $cachedata = null; 667 | try { 668 | $cachedata = $this->getData($k); 669 | 670 | if (!$cachedata->checkUpdateToDate($this)) { 671 | // stale 672 | $cachedata = null; 673 | } 674 | // TODO $this->prefetch($cachedata->getDependencies()); 675 | } 676 | catch (Exceptions\NotCachedException $e) { 677 | } 678 | 679 | // found. just return it. 680 | if ($cachedata) { 681 | try { 682 | $this->log( 683 | CacheLogEnum::ACCESSED, 684 | $cachedata->key, 685 | $cachedata->lifetime 686 | ); 687 | $key = "cache-" . rand(); 688 | 689 | $retval = $cachedata->stringify($this); 690 | 691 | if ($print) { 692 | // @codeCoverageIgnoreStart 693 | if (static::$debugOnPage) { 694 | $this->printProbeStart($key, $cachedata, 'hit'); 695 | } 696 | // @codeCoverageIgnoreEnd 697 | 698 | echo $retval; 699 | 700 | // @codeCoverageIgnoreStart 701 | if (static::$debugOnPage) { 702 | $this->printProbeEnd($key, $cachedata); 703 | } 704 | // @codeCoverageIgnoreEnd 705 | } 706 | return $retval; 707 | } 708 | catch (Exceptions\NotCachedException $e) { 709 | $this->delete($k); // clear recursively 710 | if ($this->inloop) { 711 | throw $e; 712 | } 713 | } 714 | } 715 | if ($fail) { 716 | throw new Exceptions\NotCachedException(); 717 | } 718 | 719 | $this->inloop++; 720 | $cd = new CacheData($k); 721 | $cd->setLifetime($lifetime ? $lifetime : $this->lifetime); 722 | $this->loopdata[$this->inloop] = $cd; 723 | 724 | if ($this->inloop > 1) { 725 | // we are recursive. push whatever we have so far in the previous cache 726 | $data = ob_get_contents(); 727 | ob_clean(); 728 | 729 | foreach ($this->loopdata as $l) { 730 | if ($l == $cd) { // don't depend on itself 731 | continue; 732 | } 733 | /* @var $l CacheData */ 734 | $l->addDependency($k); 735 | } 736 | $this->loopdata[$this->inloop - 1]->appendData($data); 737 | $this->loopdata[$this->inloop - 1]->appendRecursionData($cd); 738 | } 739 | else { 740 | // something was not cached below. We invalidated all cache 741 | // dependencies 742 | } 743 | 744 | ob_start(); 745 | ob_implicit_flush(false); 746 | 747 | return false; 748 | } 749 | 750 | /** 751 | * 752 | * @param boolean $print 753 | * @throws \Cachearium\Exceptions\CacheStoreFailure 754 | * @return string The string. If $print == true the string is printed as well. 755 | */ 756 | public function recursiveEnd($print = true) { 757 | // @codeCoverageIgnoreStart 758 | if (!$this->enabled) { 759 | return ''; 760 | } 761 | // @codeCoverageIgnoreEnd 762 | 763 | $data = ob_get_clean(); 764 | 765 | /* @var $cachedata CacheData */ 766 | $cachedata = $this->loopdata[$this->inloop]; 767 | $cachedata->appendData($data); 768 | 769 | try { 770 | $cachedata->generateDependenciesHash($this); 771 | } 772 | catch (\Cachearium\Exceptions\CacheUnsupportedOperation $e) { 773 | // not much we can do here, so just keep on going 774 | } 775 | $mainkey = $this->keyFromDeps($cachedata->getKey(), $cachedata->dependencies); 776 | if (!$this->storeP($cachedata, 'cacherecursive', 0, $mainkey)) { 777 | throw new \Cachearium\Exceptions\CacheStoreFailure("Storing key"); 778 | } 779 | if (!$this->storeData($cachedata)) { 780 | throw new \Cachearium\Exceptions\CacheStoreFailure("Storing data"); 781 | } 782 | 783 | // if recursive 784 | unset($this->loopdata[$this->inloop]); 785 | $this->inloop--; 786 | if ($this->inloop > 0) { 787 | return ''; 788 | } 789 | 790 | if ($print) { 791 | $key = "cache-" . rand(); 792 | // @codeCoverageIgnoreStart 793 | if (static::$debugOnPage) { 794 | $this->printProbeStart($key, $cachedata, 'save'); 795 | } 796 | // @codeCoverageIgnoreEnd 797 | 798 | $str = $cachedata->stringify($this); 799 | echo $str; 800 | 801 | // @codeCoverageIgnoreStart 802 | if (static::$debugOnPage) { 803 | $this->printProbeEnd($key, $cachedata); 804 | } 805 | // @codeCoverageIgnoreEnd 806 | return $str; 807 | } 808 | 809 | return $cachedata->stringify($this); 810 | } 811 | 812 | /** 813 | * Ends the cache start(). 814 | * @see recursiveEnd() 815 | */ 816 | public function end($print = true) { 817 | return $this->recursiveEnd($print); 818 | } 819 | 820 | /** 821 | * Cancels something started by recursiveStart() if you don't want to call recursiveEnd() 822 | * 823 | */ 824 | public function recursiveAbort() { 825 | // @codeCoverageIgnoreStart 826 | if (!$this->enabled) { 827 | return; 828 | } 829 | // @codeCoverageIgnoreEnd 830 | 831 | ob_end_clean(); 832 | 833 | // if recursive 834 | unset($this->loopdata[$this->inloop]); 835 | $this->inloop--; 836 | 837 | return; 838 | } 839 | 840 | /** 841 | * Alias for recursiveAbort() 842 | */ 843 | public function abort() { 844 | $this->recursiveAbort(); 845 | } 846 | 847 | /* 848 | * DEBUG 849 | */ 850 | 851 | /** 852 | * High level log for testing and debugging 853 | * 854 | * @codeCoverageIgnore 855 | */ 856 | public static function logHigh($message) { 857 | if (static::$debugLogFile) { 858 | file_put_contents(static::$debugLogFile, $message, FILE_APPEND); 859 | } 860 | } 861 | 862 | /** 863 | * Logs cache accesses for debugging 864 | * 865 | * @param string $status CacheLogEnum constant 866 | * @param CacheKey $k The message to print. 867 | * @param integer $lifetime 868 | * @codeCoverageIgnore 869 | */ 870 | protected function log($status, CacheKey $k, $lifetime = 0) { 871 | static::$summary[$status]++; 872 | 873 | if ($this->should_log == false) { 874 | return; 875 | } 876 | 877 | $bt = debug_backtrace(); 878 | foreach ($bt as $i => $d) { 879 | if (strpos($d['file'], '/Cache') === false) { 880 | // TODO: if() may not work well if user has a file called Cache 881 | $trace = $d['function'] . ' at ' . $d['file'] . ':' . $d['line']; 882 | $this->cache_log[] = array( 883 | 'status' => $status, 884 | 'message' => "(" . $k->debug() . ", $lifetime) by " . $trace 885 | ); 886 | break; 887 | } 888 | } 889 | } 890 | 891 | /** 892 | * Dumps a short HTML summary of the cache hits/misses 893 | * @codeCoverageIgnore 894 | */ 895 | public static function dumpSummary() { 896 | echo '
Cache Summary (non-ajax): '; 897 | foreach (static::getLogSummary() as $key => $val) { 898 | echo $key . '=>' . $val . ' / '; 899 | } 900 | echo '
'; 901 | } 902 | 903 | /** 904 | * Renders CSS for live view debugging of cached data. 905 | * @codeCoverageIgnore 906 | */ 907 | public static function cssDebug() { 908 | ?> 909 | [class^="cachearium-debug-probe"] { 910 | width: 10px; 911 | height: 10px; 912 | background-color: #f00; 913 | display: inline; 914 | /*visibility: hidden; */ 915 | } 916 | .cachearium-debug-overview { 917 | position: absolute; 918 | left: 0; 919 | top: 0; 920 | background-color: rgba(255, 255, 255, 1); 921 | border: 1px solid grey; 922 | z-index: 5000; 923 | } 924 | .cachearium-debug-view { 925 | position: absolute; 926 | pointer-events: none; 927 | border: 1px solid black; 928 | } 929 | 930 | .cachearium-debug-view[data-type="hit"] { 931 | background-color: rgba(0, 255, 0, 0.1); 932 | } 933 | .cachearium-debug-view[data-type="save"] { 934 | background-color: rgba(255, 0, 0, 0.1); 935 | } 936 | .cachearium-debug-view .cachearium-debug-view-innerdata { 937 | float: right; 938 | color: #000; 939 | height: 10px; 940 | width: 10px; 941 | border: 1px solid grey; 942 | pointer-events: auto; 943 | overflow: hidden; 944 | background-color: rgba(255, 0, 0, 0.7); 945 | } 946 | .cachearium-debug-view .cachearium-debug-view-innerdata:hover { 947 | width: auto; 948 | height: auto; 949 | background-color: rgba(255, 255, 255, 0.9); 950 | border: 1px solid grey; 951 | } 952 | 966 | 1028 | setKey($ck); 47 | 48 | if ($data) { 49 | $this->appendData($data); 50 | } 51 | } 52 | 53 | /** 54 | * 55 | * @param CacheKey $ck 56 | * @return CacheData 57 | */ 58 | public function setKey(CacheKey $ck) { 59 | $this->key = $ck; 60 | return $this; 61 | } 62 | 63 | /** 64 | * 65 | * @param unknown $callback 66 | * @return CacheData 67 | */ 68 | public function appendCallback(callable $callback) { 69 | $this->data[] = array('type' => self::CACHEDATA_TYPE_CALLBACK, 'data' => $callback); 70 | return $this; 71 | } 72 | 73 | /** 74 | * 75 | * @param CacheData $cd 76 | * @return CacheData 77 | */ 78 | public function mergeDependencies(CacheData $cd) { 79 | $this->dependencies = array_unique(array_merge($this->dependencies, $cd->dependencies)); 80 | return $this; 81 | } 82 | 83 | public function clearDependenciesHash() { 84 | $this->dependenciesHash = ''; 85 | } 86 | 87 | /** 88 | * Checks if dependencies are still fresh. 89 | * @param CacheAbstract $cache 90 | * @return boolean 91 | */ 92 | public function checkUpdateToDate(CacheAbstract $cache) { 93 | // no deps? bail out 94 | if (!count($this->dependencies)) { 95 | return true; 96 | } 97 | if ($this->generateDependenciesHash($cache) == $this->dependenciesHash) { 98 | return true; 99 | } 100 | return false; 101 | } 102 | 103 | /** 104 | * Init dependencies. If increment is not supported by backend return 0. 105 | * 106 | * @param CacheAbstract $cache 107 | * @param CacheKey $k 108 | * @return integer 109 | */ 110 | public function dependencyInit(CacheAbstract $cache, CacheKey $k) { 111 | try { 112 | return $cache->increment(0, $k, 0); 113 | } 114 | catch (\Cachearium\Exceptions\CacheUnsupportedOperation $e) { 115 | return 0; 116 | } 117 | } 118 | 119 | /** 120 | * Get a fresh hash based on dependencies. Does not update the current hash. 121 | * @param CacheAbstract $cache 122 | * @return string 123 | */ 124 | public function generateDependenciesHash(CacheAbstract $cache) { 125 | if (!count($this->dependencies)) { 126 | return ''; 127 | } 128 | $values = $cache->getMulti($this->dependencies, array($this, 'dependencyInit')); 129 | return md5(implode($values)); 130 | } 131 | 132 | public function updateDependenciesHash(CacheAbstract $cache) { 133 | $this->dependenciesHash = $this->generateDependenciesHash($cache); 134 | return $this; 135 | } 136 | 137 | public function updateDependenciesHashIfNull(CacheAbstract $cache) { 138 | if (!$this->dependenciesHash) { 139 | $this->dependenciesHash = $this->generateDependenciesHash($cache); 140 | } 141 | return $this; 142 | } 143 | 144 | public function getKey() { 145 | return $this->key; 146 | } 147 | 148 | public function getDependenciesHash() { 149 | return $this->dependenciesHash; 150 | } 151 | 152 | public function getDependencies() { 153 | return $this->dependencies; 154 | } 155 | 156 | /** 157 | * 158 | * @param mixed $data Any kind of data you want to store. usually strings. 159 | * @return CacheData 160 | */ 161 | public function appendData($data) { 162 | if ($data) { 163 | $this->data[] = array('type' => self::CACHEDATA_TYPE_DATA, 'data' => $data); 164 | } 165 | return $this; 166 | } 167 | 168 | /** 169 | * Convenience function. Returns the first data self::CACHEDATA_TYPE_DATA that you 170 | * stored. Returns null if there is none. 171 | * 172 | * @return any|NULL 173 | */ 174 | public function getFirstData() { 175 | foreach ($this->data as $d) { 176 | if ($d['type'] == self::CACHEDATA_TYPE_DATA) { 177 | return $d['data']; 178 | } 179 | } 180 | return null; 181 | } 182 | 183 | public function appendRecursion(CacheKey $k) { 184 | $this->addDependency($k); 185 | $this->data[] = array( 186 | 'type' => self::CACHEDATA_TYPE_RECURSION, 187 | 'data' => $k 188 | ); 189 | return $this; 190 | } 191 | 192 | public function appendRecursionData(CacheData $d) { 193 | if (!$d->getKey()) { 194 | throw new Exceptions\CacheInvalidDataException(); 195 | } 196 | $this->addDependency($d->getKey()); 197 | $this->data[] = array( 198 | 'type' => self::CACHEDATA_TYPE_RECURSION_DATA, 199 | 'data' => $d->getKey() 200 | ); 201 | return $this; 202 | } 203 | 204 | /** 205 | * Adds a dependency 206 | * @param CacheKey $k 207 | * @return CacheData This 208 | */ 209 | public function addDependency(CacheKey $k) { 210 | $this->dependencies[] = $k; 211 | $this->clearDependenciesHash(); 212 | return $this; 213 | } 214 | 215 | /** 216 | * Adds a dependency 217 | * @param array $k 218 | * @return CacheData This 219 | */ 220 | public function addDependencies(array $deps) { 221 | foreach ($deps as $k) { 222 | $this->addDependency($k); 223 | } 224 | return $this; 225 | } 226 | 227 | /** 228 | * Sets the list of dependencies and updates the dependency hash 229 | * 230 | * @param array $deps 231 | * @param CacheAbstract $cache 232 | * @return CacheData 233 | */ 234 | public function setDependencies(array $deps, CacheAbstract $cache) { 235 | $this->dependencies = []; 236 | foreach ($deps as $k) { 237 | $this->addDependency($k); 238 | } 239 | $this->generateDependenciesHash($cache); 240 | return $this; 241 | } 242 | 243 | /** 244 | * 245 | * @param integer $lifetime 246 | * @return CacheData 247 | */ 248 | public function setLifetime($lifetime) { 249 | $this->lifetime = $lifetime; 250 | return $this; 251 | } 252 | 253 | /** 254 | * Checks if a set of keys clashes with the ones used here. 255 | * @param CacheKey $k 256 | * @return boolean True if they match and there is a clash 257 | */ 258 | public function checkClash(CacheKey $k) { 259 | return ($this->key == $k); 260 | } 261 | 262 | /** 263 | * Converts this data to a string that can output. This is not a hash 264 | * key or a serialization, but an actual render for humans. 265 | * 266 | * @throws Exceptions\NotCachedException 267 | */ 268 | public function stringify(CacheAbstract $c, $recurse = true) { 269 | $retval = []; 270 | foreach ($this->data as $item) { 271 | if ($item['type'] == self::CACHEDATA_TYPE_CALLBACK) { 272 | $callback = $item['data']; 273 | if (is_callable($callback)) { 274 | $retval[] = call_user_func($callback); 275 | } 276 | else { 277 | // throw? 278 | } 279 | } 280 | else if ($item['type'] == self::CACHEDATA_TYPE_RECURSION) { 281 | if ($recurse) { 282 | $retval[] = $c->get($item['data']); 283 | } 284 | } 285 | else if ($item['type'] == self::CACHEDATA_TYPE_RECURSION_DATA) { 286 | if ($recurse) { 287 | $data = $c->getData($item['data']); 288 | $retval[] = $data->stringify($c); 289 | } 290 | } 291 | else { 292 | $retval[] = $item['data']; 293 | } 294 | } 295 | 296 | return implode('', $retval); 297 | } 298 | 299 | /** 300 | * Serialize this object to a string so we can easily store. 301 | * 302 | * @return string 303 | */ 304 | public function serialize() { 305 | return serialize($this); 306 | } 307 | 308 | /** 309 | * Unserializes data and returns a new CacheData. This is what you 310 | * should use to get the object data from the storage. 311 | * 312 | * @param string $data 313 | * @return CacheData 314 | */ 315 | static public function unserialize($data) { 316 | return unserialize($data); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/CacheKey.php: -------------------------------------------------------------------------------- 1 | base = $base; 37 | $this->id = $id; 38 | $this->sub = $sub; 39 | } 40 | 41 | public function getBase() { 42 | return $this->base; 43 | } 44 | 45 | public function getId() { 46 | return $this->id; 47 | } 48 | 49 | public function getSub() { 50 | return $this->sub; 51 | } 52 | 53 | public function setBase($base) { 54 | $this->base = $base; 55 | return $this; 56 | } 57 | 58 | public function setId($id) { 59 | $this->id = $id; 60 | return $this; 61 | } 62 | 63 | public function setSub($sub) { 64 | $this->sub = $sub; 65 | return $this; 66 | } 67 | 68 | /** 69 | * Returns a hash for key. 70 | * @return string 71 | */ 72 | public function getHash() { 73 | return md5($this->base . $this->id . $this->sub); 74 | } 75 | 76 | /** 77 | * Prints as a pretty string for debugging 78 | * @return string 79 | * @codeCoverageIgnore 80 | */ 81 | public function debug() { 82 | return $this->base . ", " . $this->id . ", " . print_r($this->sub, true); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/CacheLogEnum.php: -------------------------------------------------------------------------------- 1 | 'Accessed', 22 | self::MISSED => 'Missed', 23 | self::DELETED => 'Deleted', 24 | self::CLEANED => 'Cleaned', 25 | self::SAVED => 'Saved', 26 | self::PREFETCHED => 'Prefetched' 27 | ); 28 | } 29 | 30 | /** 31 | * Returns an array with all enum values. 32 | * @return array 33 | * @codeCoverageIgnore 34 | */ 35 | static public function getAll() { 36 | return array_keys(static::getNames()); 37 | } 38 | 39 | /** 40 | * Checks if a value is a valid grant 41 | * 42 | * @param string $value 43 | * @return boolean true if valid 44 | * @codeCoverageIgnore 45 | */ 46 | static public function valid($value) { 47 | return array_key_exists($value, static::getNames()); 48 | } 49 | 50 | /** 51 | * Given a name, returns its value or a string saying it is invalid. 52 | * 53 | * @param string $value 54 | * @return string 55 | * @codeCoverageIgnore 56 | */ 57 | static public function getName($value) { 58 | if (static::valid($value)) { 59 | $x = static::getNames(); 60 | return $x[$value]; 61 | } 62 | return 'Invalid: ' . htmlspecialchars($value); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Cached.php: -------------------------------------------------------------------------------- 1 | getId(), $atts); 11 | * 12 | * @return CacheKey 13 | */ 14 | public function getCacheKey($atts = null); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/Exceptions/CacheInvalidBackendException.php: -------------------------------------------------------------------------------- 1 | addServers([['localhost', 11211]]); // init server 16 | CacheAbstract::clearAll(); 17 | } 18 | 19 | static public function tearDownAfterClass() { 20 | } 21 | 22 | public function testFactory() { 23 | try { 24 | CacheAbstract::factory("invalidbackend"); 25 | $this->assertTrue(false); 26 | } 27 | catch (Cachearium\Exceptions\CacheInvalidBackendException $e) { 28 | $this->assertTrue(true); 29 | } 30 | } 31 | 32 | private function setGetClean(CacheAbstract $cache) { 33 | $base = 'base'; 34 | 35 | // enable 36 | $this->assertTrue($cache->isEnabled()); 37 | $cache->setEnabled(false); 38 | $this->assertFalse($cache->isEnabled()); 39 | $cache->setEnabled(true); 40 | $this->assertTrue($cache->isEnabled()); 41 | $cache->disable(); 42 | $this->assertFalse($cache->isEnabled()); 43 | $cache->enable(); 44 | $this->assertTrue($cache->isEnabled()); 45 | 46 | $cache->setDefaultLifetime(3600); 47 | $this->assertEquals(3600, $cache->getDefaultLifetime()); 48 | 49 | $key1 = new CacheKey($base, 1); 50 | $cache->clean($key1); 51 | 52 | try { 53 | $data = $cache->get($key1); 54 | $this->fail(); 55 | } 56 | catch (Cachearium\Exceptions\NotCachedException $e) { 57 | $this->assertTrue(true); 58 | } 59 | 60 | $retval = $cache->store(234, $key1); 61 | $this->assertTrue($retval); 62 | 63 | try { 64 | $data = $cache->get($key1); 65 | $this->assertEquals(234, $data); 66 | } 67 | catch (Cachearium\Exceptions\NotCachedException $e) { 68 | $this->fail(); 69 | } 70 | 71 | // sleep(1); 72 | 73 | try { 74 | $data = $cache->get($key1); 75 | $this->assertEquals(234, $data); 76 | } 77 | catch (Cachearium\Exceptions\NotCachedException $e) { 78 | $this->fail(); 79 | } 80 | 81 | $cache->clean($key1); 82 | try { 83 | $data = $cache->get($key1); 84 | $this->fail(); 85 | } 86 | catch (Cachearium\Exceptions\NotCachedException $e) { 87 | $this->assertTrue(true); 88 | } 89 | 90 | $key2 = new CacheKey($base, 2, 'a'); 91 | $key3 = new CacheKey($base, 3, 'a'); 92 | // now change again and delete 93 | $retval = $cache->store(234, $key2); 94 | $this->assertEquals(true, $retval); 95 | try { 96 | $data = $cache->get($key2); 97 | $this->assertEquals(234, $data); 98 | } 99 | catch (Cachearium\Exceptions\NotCachedException $e) { 100 | $this->fail(); 101 | } 102 | $this->assertTrue($cache->delete($key2)); 103 | 104 | // test null 105 | $retval = $cache->store(null, $key3); 106 | $this->assertEquals(true, $retval); 107 | try { 108 | $data = $cache->get($key3); 109 | $this->assertEquals(null, $data); 110 | } 111 | catch (Cachearium\Exceptions\NotCachedException $e) { 112 | $this->fail(); 113 | } 114 | $this->assertTrue($cache->delete($key3)); 115 | 116 | $this->assertArrayHasKey(CacheLogEnum::ACCESSED, $cache->getLogSummary()); 117 | $this->assertGreaterThan(0, $cache->getLogSummary()[CacheLogEnum::ACCESSED]); 118 | } 119 | 120 | public function testSetGetCleanRAM() { 121 | $cache = CacheRAM::singleton(); 122 | if ($cache->isEnabled()) { 123 | $this->setGetClean($cache); 124 | } 125 | } 126 | 127 | public function testSetGetCleanFS() { 128 | $cache = CacheFilesystem::singleton(); 129 | if ($cache->isEnabled()) { 130 | $this->setGetClean($cache); 131 | } 132 | } 133 | 134 | public function testSetGetCleanMemcached() { 135 | $cache = CacheMemcached::singleton(); 136 | if ($cache->isEnabled()) { 137 | $this->setGetClean($cache); 138 | } 139 | } 140 | 141 | private function getStoreData(CacheAbstract $cache) { 142 | $base = 'base'; 143 | 144 | $this->assertTrue($cache->isEnabled()); 145 | $cache->setDefaultLifetime(3600); 146 | $this->assertEquals(3600, $cache->getDefaultLifetime()); 147 | 148 | // clean 149 | $key1 = new CacheKey($base, 1); 150 | $cache->clean($key1); 151 | 152 | // nothing there 153 | try { 154 | $data = $cache->get($key1); 155 | $this->fail(); 156 | } 157 | catch (Cachearium\Exceptions\NotCachedException $e) { 158 | $this->assertTrue(true); 159 | } 160 | 161 | // store 162 | $cd = new CacheData($key1, 234); 163 | $retval = $cache->storeData($cd); 164 | $this->assertTrue($retval); 165 | 166 | // get 167 | try { 168 | $data = $cache->getData($key1); 169 | $this->assertInstanceOf('Cachearium\CacheData', $data); 170 | $this->assertEquals(234, $data->getFirstData()); 171 | } 172 | catch (Cachearium\Exceptions\NotCachedException $e) { 173 | $this->fail(); 174 | } 175 | 176 | // ivalid 177 | try { 178 | $data2 = $cache->getDataP("some", "random", "stuff"); 179 | $this->fail(); 180 | } 181 | catch (Cachearium\Exceptions\NotCachedException $e) { 182 | $this->assertTrue(true); 183 | } 184 | 185 | sleep(1); 186 | 187 | try { 188 | $data = $cache->getData($key1); 189 | $this->assertInstanceOf('Cachearium\CacheData', $data); 190 | $this->assertEquals(234, $data->getFirstData()); 191 | } 192 | catch (Cachearium\Exceptions\NotCachedException $e) { 193 | $this->fail(); 194 | } 195 | 196 | // clean 197 | $cache->clean($key1); 198 | try { 199 | $data = $cache->getData($key1); 200 | $this->fail(); 201 | } 202 | catch (Cachearium\Exceptions\NotCachedException $e) { 203 | $this->assertTrue(true); 204 | } 205 | 206 | // check conflicts 207 | $key2 = new CacheKey($base, 2, 'a'); 208 | $key3 = new CacheKey($base, 3, 'a'); 209 | // now change again and delete 210 | $retval = $cache->storeData(new CacheData($key2, 234)); 211 | $this->assertEquals(true, $retval); 212 | try { 213 | $data = $cache->getData($key2); 214 | $this->assertInstanceOf('Cachearium\CacheData', $data); 215 | $this->assertEquals(234, $data->getFirstData()); 216 | } 217 | catch (Cachearium\Exceptions\NotCachedException $e) { 218 | $this->fail(); 219 | } 220 | $this->assertTrue($cache->delete($key2)); 221 | 222 | // test null 223 | $retval = $cache->storeData(new CacheData($key3), null); 224 | $this->assertEquals(true, $retval); 225 | try { 226 | $data = $cache->getData($key3); 227 | $this->assertInstanceOf('Cachearium\CacheData', $data); 228 | $this->assertEquals(null, $data->getFirstData()); 229 | } 230 | catch (Cachearium\Exceptions\NotCachedException $e) { 231 | $this->fail(); 232 | } 233 | $this->assertTrue($cache->delete($key3)); 234 | } 235 | 236 | public function testgetStoreDataRAM() { 237 | $cache = CacheRAM::singleton(); 238 | if ($cache->isEnabled()) { 239 | $this->getStoreData($cache); 240 | } 241 | } 242 | 243 | public function testgetStoreDataMemcached() { 244 | $cache = CacheMemcached::singleton(); 245 | if ($cache->isEnabled()) { 246 | $this->getStoreData($cache); 247 | } 248 | } 249 | 250 | public function testgetStoreDataFS() { 251 | $cache = CacheFilesystem::singleton(); 252 | if ($cache->isEnabled()) { 253 | $this->getStoreData($cache); 254 | } 255 | } 256 | 257 | private function dependency(CacheAbstract $cache) { 258 | // store 259 | $key1 = new CacheKey('Namespace', 'Subname'); 260 | $cd = new CacheData($key1, 'xxxx'); 261 | $depkey = new CacheKey('Namespace', 'SomeDep'); 262 | $cd->addDependency($depkey); 263 | $cache->storeData($cd); 264 | 265 | // check if it is cached 266 | try { 267 | $data = $cache->getData($key1); 268 | $this->assertInstanceOf('Cachearium\CacheData', $data); 269 | $this->assertEquals('xxxx', $data->getFirstData()); 270 | } 271 | catch (Cachearium\Exceptions\NotCachedException $e) { 272 | $this->fail(); 273 | } 274 | 275 | // invalidate a dependency 276 | $cache->invalidate($depkey); 277 | 278 | // get the original and it should be uncached 279 | try { 280 | $data = $cache->getData($key1); 281 | $this->fail(); 282 | } 283 | catch (Cachearium\Exceptions\NotCachedException $e) { 284 | $this->assertTrue(true); 285 | } 286 | } 287 | 288 | public function testdependencyRAM() { 289 | $cache = CacheRAM::singleton(); 290 | if ($cache->isEnabled()) { 291 | $this->dependency($cache); 292 | } 293 | } 294 | 295 | public function testdependencyMemcached() { 296 | $cache = CacheMemcached::singleton(); 297 | if ($cache->isEnabled()) { 298 | $this->dependency($cache); 299 | } 300 | } 301 | 302 | public function testdependencyFS() { 303 | $this->markTestSkipped(); 304 | $cache = CacheFilesystem::singleton(); 305 | if ($cache->isEnabled()) { 306 | $this->dependency($cache); 307 | } 308 | } 309 | 310 | public function clear(CacheAbstract $cache) { 311 | $key = new CacheKey('clear', 'it'); 312 | $cd = new CacheData($key, 789); 313 | $this->assertTrue($cache->storeData($cd)); 314 | $cache->clear(); 315 | try { 316 | $cache->get($key); 317 | $this->fail(); 318 | } 319 | catch (Cachearium\Exceptions\NotCachedException $e) { 320 | $this->assertTrue(true); 321 | } 322 | } 323 | 324 | public function testClearRAM() { 325 | $cache = CacheRAM::singleton(); 326 | if ($cache->isEnabled()) { 327 | $this->clear($cache); 328 | } 329 | } 330 | 331 | public function testClearMemcached() { 332 | $cache = CacheMemcached::singleton(); 333 | if ($cache->isEnabled()) { 334 | $this->clear($cache); 335 | } 336 | } 337 | 338 | public function testClearFS() { 339 | $cache = CacheFilesystem::singleton(); 340 | if ($cache->isEnabled()) { 341 | $this->clear($cache); 342 | } 343 | } 344 | public function increment(CacheAbstract $cache) { 345 | $key = new CacheKey('increment', 'it'); 346 | $cache->delete($key); 347 | $this->assertEquals(5, $cache->increment(1, $key, 5)); 348 | $this->assertEquals(6, $cache->increment(1, $key, 5)); 349 | } 350 | 351 | public function testIncrementRAM() { 352 | $cache = CacheRAM::singleton(); 353 | if ($cache->isEnabled()) { 354 | $this->increment($cache); 355 | } 356 | } 357 | 358 | public function testIncrementMemcached() { 359 | $cache = CacheMemcached::singleton(); 360 | if ($cache->isEnabled()) { 361 | $this->increment($cache); 362 | } 363 | } 364 | 365 | public function testIncrementFS() { 366 | $cache = CacheFilesystem::singleton(); 367 | if ($cache->isEnabled()) { 368 | try { 369 | $this->increment($cache); 370 | $this->fail(); 371 | } 372 | catch (Cachearium\Exceptions\CacheUnsupportedOperation $e) { 373 | $this->assertTrue(true); 374 | } 375 | } 376 | } 377 | 378 | public function testClearAll() { 379 | $cacheKey = new CacheKey('test', 1); 380 | $cacheData = new CacheData($cacheKey, 'test'); 381 | 382 | $caches = [CacheRAM::singleton(), CacheFilesystem::singleton()]; 383 | if (CacheMemcached::hasMemcachedExt()) { 384 | $caches[] = CacheMemcached::singleton(); 385 | } 386 | foreach($caches as $cacheInst) { 387 | $cacheInst->enable()->storeData($cacheData); 388 | $retrievedData = $cacheInst->getData($cacheKey); 389 | $this->assertEquals($cacheData->getFirstData(), $retrievedData->getFirstData()); 390 | } 391 | 392 | CacheAbstract::clearAll(); 393 | 394 | foreach($caches as $cacheInst) { 395 | try { 396 | $retrievedData = $cacheInst->getData($cacheKey); 397 | $this->fail('Cache should be empty after a clearAll call !'); 398 | } 399 | catch(\Cachearium\Exceptions\NotCachedException $e) { 400 | $this->assertTrue(true, 'All cache was cleaned'); 401 | } 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /test/CacheCallbackTest.php: -------------------------------------------------------------------------------- 1 | addServers([['localhost', 11211]]); // init server 26 | CacheAbstract::clearAll(); 27 | } 28 | 29 | protected function _callback(CacheAbstract $cache) { 30 | $base = 'callback'; 31 | 32 | $key1 = new CacheKey($base, 1); 33 | $cache->clean($key1); 34 | 35 | $this->assertEquals(CALLBACKVALUE, $cache->startCallback($key1, 'callbackTester')); 36 | 37 | try { 38 | $data = $cache->getData($key1); 39 | $this->assertEquals(CALLBACKVALUE, $data->stringify($cache)); 40 | } 41 | catch (Cachearium\Exceptions\NotCachedException $e) { 42 | $this->fail(); 43 | } 44 | } 45 | 46 | public function testcallbackRAM() { 47 | $cache = CacheRAM::singleton(); 48 | if ($cache->isEnabled()) { 49 | $this->_callback($cache); 50 | } 51 | } 52 | 53 | public function testcallbackMemcached() { 54 | $cache = CacheMemcached::singleton(); 55 | if ($cache->isEnabled()) { 56 | $this->_callback($cache); 57 | } 58 | } 59 | 60 | public function testcallbackFS() { 61 | $cache = CacheFilesystem::singleton(); 62 | if ($cache->isEnabled()) { 63 | $this->_callback($cache); 64 | } 65 | } 66 | 67 | public function testCacheError() { 68 | $cache = CacheRAM::singleton(); 69 | $this->assertFalse($cache->appendCallback('callbackTesterStart')); 70 | } 71 | 72 | protected function _startcallback(CacheAbstract $cache) { 73 | $key = new CacheKey("startcallback", 1); 74 | $cache->clean($key); 75 | 76 | $this->assertFalse($cache->start($key)); 77 | echo "something "; 78 | $this->assertTrue($cache->appendCallback('callbackTesterStart')); 79 | echo " otherthing"; 80 | $output = $cache->end(false); 81 | 82 | $this->assertContains(CALLBACKVALUE, $output); 83 | 84 | // run again, we should have another value 85 | $second = $cache->start($key, null, false); 86 | $this->assertNotFalse($second); 87 | $this->assertContains(CALLBACKVALUE, $second); 88 | $this->assertNotEquals($second, $output); 89 | } 90 | 91 | public function teststartCallbackRAM() { 92 | $cache = CacheRAM::singleton(); 93 | if ($cache->isEnabled()) { 94 | $this->_startcallback($cache); 95 | } 96 | } 97 | 98 | public function teststartCallbackMemcached() { 99 | $cache = CacheMemcached::singleton(); 100 | if ($cache->isEnabled()) { 101 | $this->_startcallback($cache); 102 | } 103 | } 104 | 105 | public function teststartCallbackFS() { 106 | $cache = CacheFilesystem::singleton(); 107 | if ($cache->isEnabled()) { 108 | $this->_startcallback($cache); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /test/CacheDataTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($cd->key, $ck); 25 | 26 | $ck2 = new CacheKey('base', 'id', 'sub2'); 27 | $cd->setKey($ck2); 28 | $this->assertEquals($cd->key, $ck2); 29 | } 30 | 31 | public function testMultiData() { 32 | $ck1 = new CacheKey('data', 1, 'sub'); 33 | $ck2 = new CacheKey('data', 2, 'sub'); 34 | $ck3 = new CacheKey('data', 3, 'sub'); 35 | $this->assertNotNull($ck1); 36 | $this->assertNotNull($ck2); 37 | $this->assertNotNull($ck3); 38 | $cd2 = new CacheData($ck2, 'is'); 39 | $cd3 = new CacheData($ck3, 'data'); 40 | $this->assertNotNull($cd2); 41 | $this->assertNotNull($cd3); 42 | $this->markTestIncomplete(); 43 | } 44 | 45 | public function testCallback() { 46 | $cache = Cachearium\Backend\CacheRAM::singleton(); 47 | $ck1 = new CacheKey('callback', 1, 'sub'); 48 | $ck2 = new CacheKey('callback', 2, 'sub'); 49 | $cd1 = new CacheData($ck1, null); 50 | $cd2 = new CacheData($ck2, null); 51 | 52 | $cd2->appendCallback('callbackDataTester'); 53 | $cd1->appendData('something'); 54 | $cd1->appendRecursionData($cd2); 55 | $this->assertTrue($cache->storeData($cd2)); 56 | $this->assertTrue($cache->storeData($cd1)); 57 | $this->assertNotFalse($cache->getData($ck2)); 58 | $this->assertNotFalse($cache->getData($ck1)); 59 | 60 | $this->assertEquals('something' . CALLBACKDATATESTERVALUE, $cd1->stringify($cache)); 61 | } 62 | 63 | public function testDependencies() { 64 | $cache = Cachearium\Backend\CacheRAM::singleton(); 65 | 66 | $ck1 = new CacheKey('recursion', 1, 'sub'); 67 | $ck2 = new CacheKey('recursion', 2, 'sub'); 68 | $ck3 = new CacheKey('recursion', 3, 'sub'); 69 | $cd1 = new CacheData($ck1, 'this'); 70 | $cd2 = new CacheData($ck2, 'is'); 71 | $cd3 = new CacheData($ck3, 'recursion'); 72 | $cd2->appendRecursionData($cd3); 73 | $cd1->appendRecursionData($cd2); 74 | $this->assertTrue($cache->storeData($cd3)); 75 | $this->assertTrue($cache->storeData($cd2)); 76 | $this->assertTrue($cache->storeData($cd1)); 77 | $this->assertNotFalse($cache->getData($ck1)); 78 | $this->assertNotFalse($cache->getData($ck2)); 79 | $this->assertNotFalse($cache->getData($ck3)); 80 | 81 | $this->assertEquals('thisisrecursion', $cd1->stringify($cache)); 82 | 83 | $cd2 = new CacheData($ck2, 'breaks'); 84 | $this->assertTrue($cache->storeData($cd2)); 85 | try { 86 | $cache->getData($ck1); 87 | $this->fail(); 88 | } 89 | catch (Cachearium\Exceptions\NotCachedException $e) { 90 | $this->assertTrue(true); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/CacheInterfaceTest.php: -------------------------------------------------------------------------------- 1 | cacheClean(); 22 | 23 | $k = $c->getCacheKey(); 24 | $this->assertInstanceOf('Cachearium\CacheKey', $k); 25 | $this->assertEquals('MockCachedClass', $k->getBase()); 26 | $this->assertEquals(1, $k->getId()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/CacheKeyTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('base', $ck->getBase()); 11 | $this->assertEquals('id', $ck->getId()); 12 | $this->assertEquals('sub', $ck->getSub()); 13 | 14 | $ck = new CacheKey(null, null); 15 | $ck->setBase('base') 16 | ->setId('id') 17 | ->setSub('sub'); 18 | $this->assertEquals('base', $ck->getBase()); 19 | $this->assertEquals('id', $ck->getId()); 20 | $this->assertEquals('sub', $ck->getSub()); 21 | $this->assertNotNull($ck->getHash()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/CacheLogEnumTest.php: -------------------------------------------------------------------------------- 1 | assertGreaterThan(0, count(Cachearium\CacheLogEnum::getNames())); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/CacheMemcachedTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The Memcached extension is not available.'); 13 | } 14 | // ob_start(); 15 | } 16 | 17 | public function testNamespace() { 18 | $cache = CacheMemcached::singleton(); 19 | $this->assertEquals($cache, $cache->setNamespace("testmem")); 20 | $this->assertEquals("testmem", $cache->getNamespace()); 21 | 22 | $key = new CacheKey('namespace', 1); 23 | $cache->store(333, $key); 24 | try { 25 | $data = $cache->get($key); 26 | } 27 | catch(Cachearium\Exceptions\NotCachedException $e) { 28 | $this->fail(); 29 | } 30 | 31 | $this->assertEquals($cache, $cache->setNamespace("other")); 32 | try { 33 | $data = $cache->get($key); 34 | $this->fail(); 35 | } 36 | catch(Cachearium\Exceptions\NotCachedException $e) { 37 | $this->assertTrue(true); 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /test/CacheNullTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Cachearium\Backend\CacheNull', $cache); 16 | 17 | $cache = CacheAbstract::factory('Null'); 18 | $this->assertInstanceOf('Cachearium\Backend\CacheNull', $cache); 19 | 20 | try { 21 | $cache->get($key); 22 | $this->assertTrue(false); 23 | } 24 | catch (Cachearium\Exceptions\NotCachedException $e) { 25 | $this->assertTrue(true); 26 | } 27 | 28 | $this->assertEquals(5, $cache->increment(1, $key, 5)); 29 | $this->assertTrue($cache->store(10, $key)); 30 | $this->assertTrue($cache->delete($key)); 31 | $this->assertTrue($cache->clean($key)); 32 | $this->assertTrue($cache->clear()); 33 | $this->assertFalse($cache->start($key)); 34 | $cache->end(); 35 | $cache->prefetch(array()); 36 | $cache->enable(true); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/CacheRamTest.php: -------------------------------------------------------------------------------- 1 | start($key)) { 23 | $this->assertTrue(true); 24 | echo $expected; 25 | } 26 | else { 27 | $this->assertTrue(false); 28 | } 29 | $data = $cache->end(false); 30 | 31 | // check if we have the correct data 32 | $this->assertEquals($expected, $data); 33 | 34 | // now it should be cached 35 | $this->assertEquals($expected, $cache->start($key, null, false)); 36 | } 37 | 38 | public function testStartBasicRAM() { 39 | $cache = CacheRAM::singleton(); 40 | if ($cache->isEnabled()) { 41 | $this->_startBasic($cache); 42 | } 43 | } 44 | 45 | private function _startMultiLevel(CacheAbstract $cache) { 46 | $expected = ["first level", "second level 1", "second level 2", "second level 3"]; 47 | $i = 0; 48 | if (!$cache->start(new CacheKey('startmultilevel', 'first', 0), null, false)) { 49 | $this->assertTrue(true); 50 | echo $expected[$i++]; 51 | if (!$cache->start(new CacheKey('startmultilevel', 'second', $i), null, false)) { 52 | echo $expected[$i++]; 53 | $cache->end(); 54 | } 55 | if (!$cache->start(new CacheKey('startmultilevel', 'second', $i), null, false)) { 56 | echo $expected[$i++]; 57 | $cache->end(); 58 | } 59 | if (!$cache->start(new CacheKey('startmultilevel', 'second', $i), null, false)) { 60 | echo $expected[$i++]; 61 | $cache->end(); 62 | } 63 | } 64 | else { 65 | $this->assertTrue(false); 66 | } 67 | $data = $cache->end(false); 68 | $this->assertEquals(implode('', $expected), $data); 69 | } 70 | 71 | public function testStartMultiLevel() { 72 | $cache = CacheRAM::singleton(); 73 | if ($cache->isEnabled()) { 74 | $this->_startMultiLevel($cache); 75 | } 76 | } 77 | 78 | private function _startNested(CacheAbstract $cache) { 79 | $expected = ["first level", "second level 1", "second level 2", "second level 3"]; 80 | $i = 0; 81 | $data = ''; 82 | if (!($data = $cache->start(new CacheKey('startnested', 'first', 0), null, false))) { 83 | $this->assertTrue(true); 84 | echo $expected[$i++]; 85 | if (!$cache->start(new CacheKey('startnested', 'second', $i), null, false)) { 86 | echo $expected[$i++]; 87 | if (!$cache->start(new CacheKey('startnested', 'second', $i), null, false)) { 88 | echo $expected[$i++]; 89 | if (!$cache->start(new CacheKey('startnested', 'second', $i), null, false)) { 90 | echo $expected[$i++]; 91 | $cache->end(); 92 | } 93 | $cache->end(); 94 | } 95 | $cache->end(); 96 | } 97 | $data = $cache->end(false); 98 | } 99 | else { 100 | $this->assertTrue(false); 101 | } 102 | $this->assertEquals(implode('', $expected), $data); 103 | } 104 | 105 | public function testStartNested() { 106 | $cache = CacheRAM::singleton(); 107 | if ($cache->isEnabled()) { 108 | $this->_startNested($cache); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /test/CacheTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The Memcached extension is not available.'); 47 | } 48 | // ob_start(); 49 | } 50 | 51 | protected function tearDown() { 52 | // ob_end_clean(); 53 | } 54 | 55 | private function setGetClean(CacheAbstract $cache) { 56 | $base = 'base'; 57 | 58 | $this->assertTrue($cache->isEnabled()); 59 | $cache->setDefaultLifetime(3600); 60 | $this->assertEquals(3600, $cache->getDefaultLifetime()); 61 | $cache->cleanP($base, 1); 62 | 63 | try { 64 | $data = $cache->getP($base, 1); 65 | $this->fail(); 66 | } 67 | catch(Cachearium\Exceptions\NotCachedException $e) { 68 | $this->assertTrue(true); 69 | } 70 | 71 | $retval = $cache->storeP(234, $base, 1); 72 | $this->assertTrue($retval); 73 | 74 | try { 75 | $data = $cache->getP($base, 1); 76 | $this->assertEquals(234, $data); 77 | } 78 | catch(Cachearium\Exceptions\NotCachedException $e) { 79 | $this->fail(); 80 | } 81 | 82 | sleep(1); 83 | 84 | try { 85 | $data = $cache->getP($base, 1); 86 | $this->assertEquals(234, $data); 87 | } 88 | catch(Cachearium\Exceptions\NotCachedException $e) { 89 | $this->fail(); 90 | } 91 | 92 | $cache->cleanP($base, 1); 93 | try { 94 | $data = $cache->getP($base, 1); 95 | $this->fail(); 96 | } 97 | catch(Cachearium\Exceptions\NotCachedException $e) { 98 | $this->assertTrue(true); 99 | } 100 | 101 | // now change again and delete 102 | $retval = $cache->storeP(234, $base, 2, 'a'); 103 | $this->assertEquals(true, $retval); 104 | try { 105 | $data = $cache->getP($base, 2, 'a'); 106 | $this->assertEquals(234, $data); 107 | } 108 | catch(Cachearium\Exceptions\NotCachedException $e) { 109 | $this->fail(); 110 | } 111 | $this->assertTrue($cache->deleteP($base, 2, 'a')); 112 | 113 | // test null 114 | $retval = $cache->storeP(null, $base, 3, 'a'); 115 | $this->assertEquals(true, $retval); 116 | try { 117 | $data = $cache->getP($base, 3, 'a'); 118 | $this->assertEquals(null, $data); 119 | } 120 | catch(Cachearium\Exceptions\NotCachedException $e) { 121 | $this->fail(); 122 | } 123 | $this->assertTrue($cache->deleteP($base, 3, 'a')); 124 | } 125 | 126 | public function testSetGetCleanRAM() { 127 | $cache = CacheRAM::singleton(); 128 | $this->setGetClean($cache); 129 | } 130 | 131 | public function testSetGetCleanFS() { 132 | $cache = CacheFilesystem::singleton(); 133 | $this->setGetClean($cache); 134 | } 135 | 136 | public function testSetGetCleanMem() { 137 | if (self::$testMemoryCache) { 138 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 139 | $this->setGetClean($cache); 140 | } 141 | } 142 | 143 | public function testPrefetch() { 144 | if (!self::$testMemoryCache) { 145 | return; 146 | } 147 | 148 | $key = new CacheKey('baseprefetch', 'idprefetch', 'subprefetch'); 149 | $data = '92348ijasd2q3r'; 150 | 151 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 152 | 153 | // save. 154 | $retval = $cache->store($data, $key); 155 | $this->assertEquals(true, $retval); 156 | 157 | // get. We should have a local cache. 158 | $startfetches = $cache->getFetches(); 159 | $data2 = $cache->get($key); 160 | $this->assertEquals($data, $data2); 161 | $this->assertEquals($startfetches, $cache->getFetches()); 162 | 163 | // now clear the cache. and prefetch 164 | $cache->prefetchClear(); 165 | $cache->prefetch( 166 | array($key) 167 | ); 168 | 169 | $this->markTestSkipped(); 170 | // get. We should have a local cache. 171 | $startfetches = $cache->getFetches(); 172 | $data2 = $cache->get($key); 173 | $this->assertEquals($data, $data2); 174 | $this->assertEquals($startfetches, $cache->getFetches()); 175 | } 176 | 177 | private function startEnd(CacheAbstract $cache) { 178 | $base = 'startend'; 179 | $cache->cleanP($base, 1); 180 | 181 | $this->assertFalse($cache->recursiveStartP($base, 1)); 182 | echo 'start!'; 183 | $cache->recursiveend(false); 184 | 185 | ob_start(); 186 | ob_implicit_flush(false); 187 | $this->assertTrue(($cache->recursiveStartP($base, 1) !== false)); 188 | $data = ob_get_contents(); 189 | ob_end_clean(); 190 | 191 | $this->assertEquals('start!', $data); 192 | $cache->cleanP($base, 1); 193 | } 194 | 195 | public function testStartEndRAM() { 196 | $cache = CacheRAM::singleton(); 197 | $this->startEnd($cache); 198 | } 199 | 200 | public function testStartEndFS() { 201 | $cache = CacheFilesystem::singleton(); 202 | $this->startEnd($cache); 203 | } 204 | 205 | public function testStartEndMem() { 206 | if (self::$testMemoryCache) { 207 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 208 | $this->startEnd($cache); 209 | } 210 | } 211 | 212 | private function serialize(CacheAbstract $cache) { 213 | $base = 'serialize'; 214 | 215 | $cache->cleanP($base, 1); 216 | 217 | $data = array('awer' => 132, 218 | array('awerawer' => 23423, 219 | 'cvbxxcv' => 234, 220 | ) 221 | ); 222 | $retval = $cache->storeP($data, $base, 1); 223 | $this->assertEquals(true, $retval); 224 | 225 | $data2 = $cache->getP($base, 1); 226 | $this->assertEquals($data2, $data); 227 | } 228 | 229 | public function testSerializeRAM() { 230 | $cache = CacheRAM::singleton(); 231 | $this->serialize($cache); 232 | } 233 | 234 | public function testSerializeFS() { 235 | $cache = CacheFilesystem::singleton(); 236 | $this->serialize($cache); 237 | } 238 | 239 | public function testSerializeMem() { 240 | if (self::$testMemoryCache) { 241 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 242 | $this->serialize($cache); 243 | } 244 | } 245 | 246 | private function setBigClean(CacheAbstract $cache) { 247 | $id = '2'; 248 | $base = 'bigclean'; 249 | $otherid = '3'; 250 | 251 | $retval = $cache->storeP(111, $base, $id, 'a'); 252 | $this->assertEquals(true, $retval); 253 | $retval = $cache->storeP(222, $base, $id, 'b'); 254 | $this->assertEquals(true, $retval); 255 | $retval = $cache->storeP(333, $base, $otherid, 'a'); 256 | $this->assertEquals(true, $retval); 257 | 258 | $data = $cache->getP($base, $id, 'a'); 259 | $this->assertEquals(111, $data); 260 | $data = $cache->getP($base, $id, 'b'); 261 | $this->assertEquals(222, $data); 262 | $data = $cache->getP($base, $otherid, 'a'); 263 | $this->assertEquals(333, $data); 264 | 265 | $cache->cleanP($base, $id); 266 | 267 | try { 268 | $data = $cache->getP($base, $id, 'a'); 269 | $this->fail(); 270 | } 271 | catch(Cachearium\Exceptions\NotCachedException $e) { 272 | $this->assertTrue(true); 273 | } 274 | try { 275 | $data = $cache->getP($base, $otherid, 'a'); 276 | $this->assertEquals(333, $data); 277 | } 278 | catch(Cachearium\Exceptions\NotCachedException $e) { 279 | $this->fail(); 280 | } 281 | 282 | try { 283 | $data = $cache->getP($base, $id, 'b'); 284 | $this->fail(); 285 | } 286 | catch(Cachearium\Exceptions\NotCachedException $e) { 287 | $this->assertTrue(true); 288 | } 289 | } 290 | 291 | private function setBigClear(CacheAbstract $cache) { 292 | $id = '2'; 293 | $base = 'bigclean'; 294 | $otherid = '3'; 295 | $otherbase = 'bigfoo'; 296 | 297 | $retval = $cache->storeP(111, $base, $id, 'a'); 298 | $this->assertEquals(true, $retval); 299 | $retval = $cache->storeP(222, $base, $id, 'b'); 300 | $this->assertEquals(true, $retval); 301 | $retval = $cache->storeP(333, $base, $otherid, 'a'); 302 | $this->assertEquals(true, $retval); 303 | $retval = $cache->storeP(444, $otherbase, $otherid, 'a'); 304 | $this->assertEquals(true, $retval); 305 | 306 | $data = $cache->getP($base, $id, 'a'); 307 | $this->assertEquals(111, $data); 308 | $data = $cache->getP($base, $id, 'b'); 309 | $this->assertEquals(222, $data); 310 | $data = $cache->getP($base, $otherid, 'a'); 311 | $this->assertEquals(333, $data); 312 | $data = $cache->getP($otherbase, $otherid, 'a'); 313 | $this->assertEquals(444, $data); 314 | 315 | $cache->clear(); 316 | 317 | try { 318 | $data = $cache->getP($base, $id, 'a'); 319 | $this->fail(); 320 | } 321 | catch(Cachearium\Exceptions\NotCachedException $e) { 322 | $this->assertTrue(true); 323 | } 324 | try { 325 | $data = $cache->getP($base, $id, 'b'); 326 | $this->fail(); 327 | } 328 | catch(Cachearium\Exceptions\NotCachedException $e) { 329 | $this->assertTrue(true); 330 | } 331 | try { 332 | $data = $cache->getP($base, $otherid, 'a'); 333 | $this->fail(); 334 | } 335 | catch(Cachearium\Exceptions\NotCachedException $e) { 336 | $this->assertTrue(true); 337 | } 338 | try { 339 | $data = $cache->getP($otherbase, $otherid, 'a'); 340 | $this->fail(); 341 | } 342 | catch(Cachearium\Exceptions\NotCachedException $e) { 343 | $this->assertTrue(true); 344 | } 345 | } 346 | 347 | public function testCleanRAM() { 348 | $cache = CacheRAM::singleton(); 349 | $this->setBigClean($cache); 350 | $this->setBigClear($cache); 351 | } 352 | 353 | public function testCleanFS() { 354 | $cache = CacheFilesystem::singleton(); 355 | $this->setBigClean($cache); 356 | $this->setBigClear($cache); 357 | } 358 | 359 | public function testCleanMem() { 360 | if (self::$testMemoryCache) { 361 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 362 | $this->setBigClean($cache); 363 | $this->setBigClear($cache); 364 | } 365 | } 366 | 367 | private function dependencies(CacheAbstract $cache) { 368 | $this->assertFalse($cache->appendCallback('cacheCallback')); // not in loop 369 | 370 | $cache->recursivestart('parent', 1); 371 | echo 'parent start/'; 372 | $this->assertFalse($cache->appendCallback('nawreanaweroi')); // invalid 373 | $cache->appendCallback('cacheCallback'); 374 | $cache->recursivestart('child', 2); 375 | echo '|child first|'; 376 | $cache->recursiveend(false); 377 | echo 'parent end/'; 378 | $cache->recursiveend(false); 379 | 380 | $data = $cache->get('child', 2); 381 | $cachedata = CacheData::unserialize($data); 382 | $this->assertEquals('|child first|', $cachedata->stringify($cache)); 383 | $data = $cache->get('parent', 1); 384 | $cachedata = CacheData::unserialize($data); 385 | $this->assertEquals('parent start/aaaa|child first|parent end/', $cachedata->stringify($cache)); 386 | 387 | $cache->delete('child', 2); 388 | $cache->recursivestart('child', 2); 389 | echo '|child second|'; 390 | $cache->recursiveend(false); 391 | 392 | $data = $cache->get('child', 2); 393 | $cachedata = CacheData::unserialize($data); 394 | $this->assertEquals('|child second|', $cachedata->stringify($cache)); 395 | $data = $cache->get('parent', 1); 396 | $cachedata = CacheData::unserialize($data); 397 | $this->assertEquals('parent start/bbbb|child second|parent end/', $cachedata->stringify($cache)); 398 | } 399 | 400 | public function testDependenciesRAM() { 401 | $this->markTestIncomplete(); 402 | return; 403 | $cache = CacheRAM::singleton(); 404 | $this->dependencies($cache); 405 | } 406 | 407 | public function _dependencies(CacheAbstract $cache) { 408 | $cache->start('parent', 1); 409 | $cache->addDependency(); 410 | echo 'parent start/'; 411 | $this->assertFalse($cache->appendCallback('nawreanaweroi')); // invalid 412 | $cache->appendCallback('cacheCallback'); 413 | $cache->start('child', 2); 414 | echo '|child first|'; 415 | $cache->end(false); 416 | echo 'parent end/'; 417 | $cache->end(false); 418 | } 419 | 420 | public function testClearRAM() { 421 | $cache = CacheRAM::singleton(); 422 | $this->setBigClean($cache); 423 | } 424 | 425 | public function testClearFS() { 426 | $cache = CacheFilesystem::singleton(); 427 | $this->setBigClean($cache); 428 | } 429 | 430 | public function testClearMem() { 431 | if (self::$testMemoryCache) { 432 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 433 | $this->setBigClean($cache); 434 | } 435 | } 436 | 437 | public function testClash() { 438 | $cache = CacheMemcached::singleton([['localhost', 11211]]); 439 | 440 | try { 441 | ob_start(); 442 | if (!$cache->startP('testClash', 0)) { 443 | if (!$cache->startP('testClash', 0)) { 444 | $cache->end(); 445 | } 446 | $cache->end(); 447 | } 448 | $this->assertFalse(true); 449 | } 450 | catch(Cachearium\Exceptions\CacheKeyClashException $e) { 451 | $this->assertTrue(true); 452 | } 453 | } 454 | } 455 | --------------------------------------------------------------------------------