├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── docs ├── backends.md ├── manifest.json └── usage.md ├── examples ├── bootstrap.php ├── call.php ├── capture.php ├── pipeline.php ├── simple.php └── wrapper.php ├── phpunit.xml.dist ├── src └── CacheCache │ ├── Backend.php │ ├── Backends │ ├── AbstractBackend.php │ ├── Apc.php │ ├── Dummy.php │ ├── File.php │ ├── Memcache.php │ ├── Memcached.php │ ├── Memory.php │ ├── Redis.php │ └── Session.php │ ├── Cache.php │ ├── CacheException.php │ ├── CacheManager.php │ ├── LoggingBackend.php │ ├── MultiCache.php │ ├── ObjectWrapper.php │ └── Pipeline.php └── tests ├── CacheCache └── Tests │ ├── CacheTest.php │ ├── CacheTestCase.php │ └── ObjectWrapperTest.php └── bootstrap.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | script: phpunit 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Maxime Bouroumeau-Fuseau 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CacheCache 2 | 3 | Caching framework for PHP 5.3+ 4 | 5 | [![Build Status](https://secure.travis-ci.org/maximebf/CacheCache.png)](http://travis-ci.org/maximebf/CacheCache) 6 | 7 | Features: 8 | 9 | - Easy retreival and storing of key, value pairs using the many available methods 10 | - Cache function calls 11 | - Available object wrapper to cache calls to methods 12 | - Pipelines ala Predis (see below) 13 | - Namespaces 14 | - TTL variations to avoid all caches to expire at the same time 15 | - Multiple backends support (apc, file, memcache(d), memory, redis, session) 16 | - [Monolog](https://github.com/Seldaek/monolog) support 17 | - Very well documented 18 | 19 | CacheCache features are exposed through a Cache object which itself uses backends to store the data. 20 | Multiple instances of Cache objects can be managed using the CacheManager. 21 | 22 | Full documentation at [http://maximebf.github.com/CacheCache/](http://maximebf.github.com/CacheCache/) 23 | 24 | Examples: 25 | 26 | $cache = new CacheCache\Cache(new CacheCache\Backends\Memory()); 27 | 28 | if (($foo = $cache->get('foo')) === null) { 29 | $foo = 'bar'; 30 | $cache->set('foo', $foo); 31 | } 32 | 33 | if (!$cache->start('foo')) { 34 | echo "bar\n"; 35 | $cache->end(); 36 | } 37 | 38 | $cache->call('sleep', array(2)); 39 | $cache->call('sleep', array(2)); // won't sleep! 40 | 41 | $r = $cache->pipeline(function($pipe) { 42 | $pipe->set('foo', 'bar'); 43 | $pipe->set('bar', 'foo'); 44 | $pipe->get('foo'); 45 | $pipe->set('foo', 'foobar'); 46 | $pipe->get('foo'); 47 | }); 48 | 49 | More examples in [examples/](https://github.com/maximebf/CacheCache/tree/master/examples) 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maximebf/cachecache", 3 | "description": "Caching library for PHP5.3", 4 | "keywords": ["cache"], 5 | "homepage": "https://github.com/maximebf/CacheCache", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [{ 9 | "name": "Maxime Bouroumeau-Fuseau", 10 | "email": "maxime.bouroumeau@gmail.com", 11 | "homepage": "http://maximebf.com" 12 | }], 13 | "require": { 14 | "php": ">=5.3.0" 15 | }, 16 | "autoload": { 17 | "psr-0": {"CacheCache": "src/"} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/backends.md: -------------------------------------------------------------------------------- 1 | 2 | # Backends 3 | 4 | ## Apc 5 | 6 | Uses PHP's builtin APC library 7 | 8 | ## Dummy 9 | 10 | To disable the cache 11 | 12 | ## File 13 | 14 | ## Memcache 15 | 16 | Uses PHP's builtin memcache library 17 | 18 | ## Memcached 19 | 20 | Needs the libmemcached pecl extension. 21 | 22 | ## Memory 23 | 24 | Stores data in an array for the time of the request 25 | 26 | ## Redis 27 | 28 | Uses the Predis library. 29 | 30 | ## Session 31 | 32 | Stores the data in the $_SESSION array. -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "CacheCache", 3 | "home": "../README.md", 4 | "github": "maximebf/CacheCache", 5 | "files": [ 6 | "usage.md", 7 | "backends.md" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | 2 | # Installation 3 | 4 | The easiest way to install CacheCache is using [Composer](https://github.com/composer/composer) 5 | with the following requirement: 6 | 7 | { 8 | "require": { 9 | "maximebf/cachecache": ">=0.1.0" 10 | } 11 | } 12 | 13 | Alternatively, you can [download the archive](https://github.com/maximebf/CacheCache/zipball/master) 14 | and add the src/ folder to PHP's include path: 15 | 16 | set_include_path('/path/to/src' . PATH_SEPARATOR . get_include_path()); 17 | 18 | CacheCache does not provide an autoloader but follows the [PSR-0 convention](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md). 19 | You can use the following snippet to autoload CacheCache classes: 20 | 21 | spl_autoload_register(function($className) { 22 | if (substr($className, 0, 10) === 'CacheCache') { 23 | $filename = str_replace('\\', DIRECTORY_SEPARATOR, trim($className, '\\')) . '.php'; 24 | require_once $filename; 25 | } 26 | }); 27 | 28 | # Usage 29 | 30 | ## The CacheManager 31 | 32 | `CacheManager` is a static class that can be used to initialize and store multiple instances 33 | of Cache objects. 34 | 35 | Storing and accessing Cache objects: 36 | 37 | $cache = new Cache(new Backends\Memory()); 38 | CacheManager::set('mycache', $cache); 39 | 40 | // ... 41 | 42 | $cache = CacheManager::get('mycache'); 43 | 44 | Cache objects can be created and initialized using an array with the `factory()` method. 45 | The following options can be defined: 46 | 47 | - backend: backend class name or Backend instance 48 | - backend_args: an array of constructor arguments for the backend 49 | - namespace 50 | - ttl 51 | - variation 52 | 53 | Example: 54 | 55 | $cache = CacheManager::factory(array( 56 | 'backend' => 'CacheCache\Backends\Memcache', 57 | 'backend_args' => array(array( 58 | 'host' => 'localhost', 59 | 'port' => 11211 60 | )) 61 | )); 62 | 63 | Finally, multiple Cache objects can be created at the same time using the `setup()` method. It takes 64 | as first parameter an array of key/value pairs where keys will be used as the cache name to be used 65 | with the `get()` method and values are an array to be used with `factory()`. The second argument can 66 | be a `Monolog\Logger` instance to enable logging. 67 | 68 | CacheManager::setup(array( 69 | 'array' => 'CacheCache\Backends\Memory', 70 | 'memcache' => array( 71 | 'backend' => 'CacheCache\Backends\Memcache', 72 | 'backend_args' => array(array( 73 | 'host' => 'localhost', 74 | 'port' => 11211 75 | )) 76 | ) 77 | )); 78 | 79 | $cache = CacheManager::get('array'); 80 | 81 | ## Simple usage 82 | 83 | Cache (and backends) objects expose the following methods to interact with the data: 84 | 85 | - `exists($id)` 86 | - `get($id, $default = null)` 87 | - `set($id, $value, $ttl = null)` 88 | - `add($id, $value, $ttl = null)` 89 | - `delete($id)` 90 | 91 | The `add()` method won't replace any existing value whereas `set()` will do. 92 | `$ttl` stands for Time To Live and will be the lifetime in seconds of the entry. 93 | 94 | If `get()` is used to retreive a non existing `$id`, the `$default` value is returned instead. 95 | 96 | Examples: 97 | 98 | $cache->set('foo', 'bar'); 99 | $cache->exists('foo'); 100 | $cache->get('foo'); 101 | $cache->delete('foo'); 102 | 103 | $cache->add('foo', 'bar', 10); 104 | $cache->exists('foo'); // true 105 | sleep(11); 106 | $cache->exists('foo'); // false 107 | 108 | if (($foo = $cache->get('foo')) === null) { 109 | $foo = 'bar'; 110 | $cache->set('foo', $foo); 111 | } 112 | 113 | To avoid manually testing entries for their existence you can use the `getset()` method: 114 | 115 | $foo = $cache->getset('foo', function() { 116 | return 'bar'; 117 | }); 118 | 119 | In this example, the closure is called only when "foo" does not exist. Another way of 120 | doing a similar operation without the use of closures is using the `load()` and `save()` methods. 121 | 122 | if (!($foo = $cache->load('foo'))) { 123 | $foo = 'bar'; 124 | $cache->save($foo); 125 | } 126 | 127 | `load()` and `save()` calls can be nested. A currently running operation (after performing a `load()`) 128 | can be cancelled using `cancel()`. 129 | 130 | if (!($foo = $cache->load('foo'))) { 131 | try { 132 | $foo = 'bar'; 133 | $cache->save($foo); 134 | } catch (Exception $e) { 135 | $cache->cancel(); 136 | $foo = 'default value'; 137 | } 138 | } 139 | 140 | ## Caching function calls 141 | 142 | The `call()` function can be used to cache function calls. It behaves the same way as 143 | `call_user_func_array()`. The cache id is generated using the function name and the serialized arguments. 144 | 145 | function do_heavy_stuff($arg) { 146 | sleep(1); 147 | return $arg; 148 | } 149 | 150 | echo $cache->call('do_heavy_stuff', array('foo')); // sleeps 1 sec 151 | echo $cache->call('do_heavy_stuff', array('bar')); // sleeps 1 sec 152 | echo $cache->call('do_heavy_stuff', array('foo')); // won't sleep 153 | 154 | ## Caching object methods 155 | 156 | Object methods can be cached by wrapping an object into a special proxy class. The object can be used 157 | as usual but all calls to its methods will be cached. The cache id for each method is computed using 158 | the method name, the serialized arguments and the serialized public properties of the object. The `wrap()` 159 | method automatically creates a namespace for all cache ids of this object which is, by default, named after the class. 160 | 161 | class MyClass { 162 | public function doHeavyStuff($arg) { 163 | sleep(1); 164 | return $arg; 165 | } 166 | } 167 | 168 | $obj = $cache->wrap(new MyClass()); 169 | echo $obj->doHeavyStuff('foo'); // sleeps 1 sec 170 | echo $obj->doHeavyStuff('foo'); // won't sleep 171 | 172 | ## Capturing content 173 | 174 | CacheCache provides multiple ways to capture echoed content. The easiest one works the same way as `load()` and `save()` 175 | but uses `start()` and `end()`. 176 | 177 | if (!$cache->start('foo')) { 178 | echo 'bar'; 179 | $cache->end(); 180 | } 181 | 182 | `cancel()` can also be used to cancel a call to `start()`. 183 | 184 | The output of a function can also be captured using the `capture()` method which works the same way as `getset()`. 185 | 186 | $foo = $cache->capture('foo', function() { 187 | echo 'bar'; 188 | }); 189 | 190 | Finally, the whole content of a page can be captured using `capturePage()`. It must be called before any 191 | content has been outputed. 192 | 193 | $cache->capturePage(); 194 | 195 | By default, the cache id will be computed from the `REQUEST_URI` and the `$_REQUEST` array and the method 196 | calls `exit()` if there is a hit. 197 | 198 | ## Multiple operations at once and pipelines 199 | 200 | Multi get and set operations are available in a similar fashion as with the libmemcached pecl extension. 201 | 202 | $cache->setMulti(array( 203 | 'foo' => 'bar', 204 | 'bar' => 'foo' 205 | )); 206 | 207 | $r = $cache->getMulti(array('foo', 'bar')); 208 | // $r = array('bar', 'foo'); 209 | 210 | CacheCache also introduces the concept of pipelines inspired by Predis. A pipeline is an object that 211 | stack operations and executes them all at the same time. Not all backends have native support for pipelines 212 | (only redis for the moment). A simple pipeline implementation based on `setMulti()` and `getMulti()` is provided 213 | for the other ones. 214 | 215 | $r = $cache->pipeline(function($pipe) { 216 | $pipe->set('foo', 'bar'); 217 | $pipe->set('bar', 'foo'); 218 | $pipe->get('foo'); 219 | $pipe->get('bar'); 220 | }); 221 | 222 | ## Namespaces 223 | 224 | Namespaces allow you to better organize cache ids. A cache namespace is simply a new Cache object bound to 225 | a specific namespace. To create a subnamespace of the current one, use the `ns()` method. 226 | 227 | $cache->set('a', 'b'); // id = a 228 | 229 | $foo = $cache->ns('foo'); 230 | $foo->set('a', 'c'); // id = foo:a 231 | 232 | ## Multiple caches 233 | 234 | Multiple backends can be chained together using the `MultiCache` class. When a value is retreived (get or exists) 235 | it will first try in the first backend then in the second if it misses, then the third, etc... 236 | When a key/value pair is modified (insert, add or delete), the operation will be performed on all backends. 237 | 238 | $cache = new MultiCache(array($backend1, $backend2)); 239 | 240 | NOTE: Cache objects are themselves backends! 241 | 242 | ## Logging 243 | 244 | Backend usage can be logged using the `LoggingBackend` class which requires a `Monolog\Logger` instance. 245 | 246 | $logger = new Monolog\Logger(); 247 | $backend = new LoggingBackend(new Backends\Memory(), $logger); 248 | $cache = new Cache($backend); 249 | -------------------------------------------------------------------------------- /examples/bootstrap.php: -------------------------------------------------------------------------------- 1 | call('sayhello', array('peter')); 13 | echo $cache->call('sayhello', array('paul')); 14 | echo $cache->call('sayhello', array('peter')); -------------------------------------------------------------------------------- /examples/capture.php: -------------------------------------------------------------------------------- 1 | start('foo')) { 7 | echo "bar\n"; 8 | $cache->end(); 9 | } 10 | 11 | if (!$cache->start('foo')) { 12 | echo "foobar\n"; 13 | $cache->end(); 14 | } 15 | 16 | var_dump($cache->get('foo')); 17 | $cache->delete('foo'); -------------------------------------------------------------------------------- /examples/pipeline.php: -------------------------------------------------------------------------------- 1 | pipeline(function($pipe) { 7 | $pipe->set('foo', 'bar'); 8 | $pipe->set('bar', 'foo'); 9 | $pipe->get('foo'); 10 | $pipe->set('foo', 'foobar'); 11 | $pipe->get('foo'); 12 | }); 13 | var_dump($r); 14 | 15 | $cache->delete('foo'); 16 | $cache->delete('bar'); -------------------------------------------------------------------------------- /examples/simple.php: -------------------------------------------------------------------------------- 1 | exists('foo')); 7 | 8 | if (($foo = $cache->get('foo')) === null) { 9 | $foo = 'bar'; 10 | $cache->set('foo', $foo); 11 | } 12 | var_dump($foo); 13 | var_dump($cache->get('foo')); 14 | 15 | $cache->delete('foo'); 16 | var_dump($cache->exists('foo')); 17 | var_dump($cache->get('foo')); 18 | 19 | // ---------------------- 20 | 21 | $foo = $cache->getset('foo', function() { 22 | return 'bar'; 23 | }); 24 | var_dump($foo); 25 | var_dump($cache->get('foo')); 26 | 27 | $cache->delete('foo'); 28 | 29 | // ---------------------- 30 | 31 | $ns = $cache->ns('foobar'); 32 | $ns->set('foo', 'bar'); 33 | var_dump($ns->get('foo')); 34 | $ns->delete('foo'); 35 | -------------------------------------------------------------------------------- /examples/wrapper.php: -------------------------------------------------------------------------------- 1 | wrap(new HelloWorld()); 16 | echo $obj->hello(); 17 | echo $obj->hello(); -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/CacheCache/ 17 | 18 | 19 | 20 | 21 | 22 | ./src/CacheCache/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/CacheCache/Backend.php: -------------------------------------------------------------------------------- 1 | get($id) !== null; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function getMulti(array $ids) 32 | { 33 | $values = array(); 34 | foreach ($ids as $id) { 35 | $values[] = $this->get($id); 36 | } 37 | return $values; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function add($id, $value, $ttl = null) 44 | { 45 | return $this->set($id, $value, $ttl); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function setMulti(array $items, $ttl = null) 52 | { 53 | foreach ($items as $id => $value) { 54 | if (!$this->set($id, $value, $ttl)) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function supportsPipelines() 65 | { 66 | return false; 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function createPipeline() 73 | { 74 | return null; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Apc.php: -------------------------------------------------------------------------------- 1 | dir = rtrim($dir, DIRECTORY_SEPARATOR); 45 | $this->subDirs = isset($options['sub_dirs']) && $options['sub_dirs']; 46 | $this->idAsFilename = isset($options['id_as_filename']) && $options['id_as_filename']; 47 | $this->fileExtension = isset($options['file_extension']) ? $options['file_extension'] : ''; 48 | } 49 | 50 | public function exists($id) 51 | { 52 | return file_exists($this->filename($id)); 53 | } 54 | 55 | public function get($id) 56 | { 57 | if ($this->exists($id)) { 58 | $filename = $this->filename($id); 59 | list($value, $expire) = $this->decode(file_get_contents($filename)); 60 | if ($expire !== null && $expire < time()) { 61 | $this->delete($id); 62 | return null; 63 | } 64 | return $value; 65 | } 66 | return null; 67 | } 68 | 69 | public function set($id, $value, $ttl = null) 70 | { 71 | $filename = $this->filename($id); 72 | $dirname = dirname($filename); 73 | if (!file_exists($dirname)) { 74 | mkdir($dirname, 0777, true); 75 | } 76 | file_put_contents($filename, $this->encode($value, $ttl)); 77 | } 78 | 79 | public function delete($id) 80 | { 81 | if ($this->exists($id)) { 82 | unlink($this->filename($id)); 83 | } 84 | return true; 85 | } 86 | 87 | public function flushAll() 88 | { 89 | foreach (new \DirectoryIterator($this->dir) as $file) { 90 | if (substr($file->getFilename(), 0, 1) === '.' || $file->isDir()) { 91 | continue; 92 | } 93 | unlink($file->getPathname()); 94 | } 95 | return true; 96 | } 97 | 98 | /** 99 | * Generates the filename associated to an $id 100 | * 101 | * @param string $id 102 | * @return string 103 | */ 104 | public function filename($id) 105 | { 106 | if (isset($this->filenameCache[$id])) { 107 | return $this->filenameCache[$id]; 108 | } 109 | 110 | $dir = $this->dir; 111 | $filename = $id; 112 | 113 | if ($this->subDirs) { 114 | $parts = explode(CacheCache\Cache::$namespaceSeparator, $id); 115 | $filename = array_pop($parts); 116 | $dir .= DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts); 117 | if (!file_exists($dir)) { 118 | mkdir($dir, 0777, true); 119 | } 120 | } 121 | 122 | if (!$this->idAsFilename) { 123 | $filename = md5($filename); 124 | } 125 | 126 | if (!empty($this->fileExtension)) { 127 | $filename .= $this->fileExtension; 128 | } 129 | 130 | $filename = $dir . DIRECTORY_SEPARATOR . $filename; 131 | $this->filenameCache[$id] = $filename; 132 | return $filename; 133 | } 134 | 135 | /** 136 | * Encodes some data to a string so it can be written to disk 137 | * 138 | * @param mixed $data 139 | * @param int $ttl 140 | * @return string 141 | */ 142 | public function encode($data, $ttl) 143 | { 144 | $expire = null; 145 | if ($ttl !== null) { 146 | $expire = time() + $ttl; 147 | } 148 | return serialize(array($data, $expire)); 149 | } 150 | 151 | /** 152 | * Decodes a string encoded by {@see encode()} 153 | * 154 | * Must returns a tuple (data, expire). Expire 155 | * can be null to signal no expiration. 156 | * 157 | * @param string $data 158 | * @return array (data, expire) 159 | */ 160 | public function decode($data) 161 | { 162 | return unserialize($data); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Memcache.php: -------------------------------------------------------------------------------- 1 | memcache = $options['memcache']; 35 | } else { 36 | $host = isset($options['host']) ? $options['host'] : 'localhost'; 37 | $port = isset($options['port']) ? $options['port'] : 11211; 38 | $this->memcache = new \Memcache(); 39 | $this->memcache->addServer($host, $port); 40 | } 41 | } 42 | 43 | public function get($id) 44 | { 45 | if (($value = $this->memcache->get($id)) === false) { 46 | return null; 47 | } 48 | return $value; 49 | } 50 | 51 | public function add($id, $value, $ttl = null) 52 | { 53 | $ttl = $ttl ?: 0; 54 | if ($ttl > 0) { 55 | $ttl = time() + $ttl; 56 | } 57 | return $this->memcache->add($id, $value, 0, $ttl); 58 | } 59 | 60 | public function set($id, $value, $ttl = null) 61 | { 62 | $ttl = $ttl ?: 0; 63 | if ($ttl > 0) { 64 | $ttl = time() + $ttl; 65 | } 66 | return $this->memcache->set($id, $value, 0, $ttl); 67 | } 68 | 69 | public function delete($id) 70 | { 71 | return $this->memcache->delete($id); 72 | } 73 | 74 | public function flushAll() 75 | { 76 | return $this->memcache->flush(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Memcached.php: -------------------------------------------------------------------------------- 1 | memcached = $options['memcached']; 35 | } else { 36 | $host = isset($options['host']) ? $options['host'] : 'localhost'; 37 | $port = isset($options['port']) ? $options['port'] : 11211; 38 | $this->memcached = new \Memcached(); 39 | $this->memcached->addServer($host, $port); 40 | } 41 | } 42 | 43 | public function get($id) 44 | { 45 | if (($value = $this->memcached->get($id)) === false) { 46 | return null; 47 | } 48 | return $value; 49 | } 50 | 51 | public function getMulti(array $ids) 52 | { 53 | $null = null; 54 | return $this->memcached->getMulti($ids, $null, \Memcached::GET_PRESERVE_ORDER); 55 | } 56 | 57 | public function add($id, $value, $ttl = null) 58 | { 59 | $ttl = $ttl ?: 0; 60 | if ($ttl > 0) { 61 | $ttl = time() + $ttl; 62 | } 63 | return $this->memcached->add($id, $value, $ttl); 64 | } 65 | 66 | public function set($id, $value, $ttl = null) 67 | { 68 | $ttl = $ttl ?: 0; 69 | if ($ttl > 0) { 70 | $ttl = time() + $ttl; 71 | } 72 | return $this->memcached->set($id, $value, $ttl); 73 | } 74 | 75 | public function setMulti(array $items, $ttl = null) 76 | { 77 | $ttl = $ttl ?: 0; 78 | if ($ttl > 0) { 79 | $ttl = time() + $ttl; 80 | } 81 | return $this->memcached->setMulti($items, $ttl); 82 | } 83 | 84 | public function delete($id) 85 | { 86 | return $this->memcached->delete($id); 87 | } 88 | 89 | public function flushAll() 90 | { 91 | return $this->memcached->flush(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Memory.php: -------------------------------------------------------------------------------- 1 | cache)) { 27 | return null; 28 | } else if (isset($this->ttls[$id]) && $this->ttls[$id] < time()) { 29 | unset($this->cache[$id]); 30 | unset($this->ttls[$id]); 31 | return null; 32 | } 33 | return $this->cache[$id]; 34 | } 35 | 36 | public function add($id, $value, $ttl = null) 37 | { 38 | if (!array_key_exists($id, $this->cache)) { 39 | return $this->set($id, $value, $ttl); 40 | } 41 | return true; 42 | } 43 | 44 | public function set($id, $value, $ttl = null) 45 | { 46 | $this->cache[$id] = $value; 47 | if ($ttl) { 48 | $this->ttls[$id] = time() + $ttl; 49 | } 50 | return true; 51 | } 52 | 53 | public function delete($id) 54 | { 55 | if (!array_key_exists($id, $this->cache)) { 56 | return false; 57 | } 58 | unset($this->cache[$id]); 59 | if (isset($this->ttls[$id])) { 60 | unset($this->ttls[$id]); 61 | } 62 | return true; 63 | } 64 | 65 | public function flushAll() 66 | { 67 | $this->cache = array(); 68 | $this->ttls = array(); 69 | return true; 70 | } 71 | 72 | public function toArray() 73 | { 74 | return $this->cache; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Redis.php: -------------------------------------------------------------------------------- 1 | redis = $options['redis']; 38 | } else { 39 | $this->redis = new Predis\Client($options); 40 | } 41 | } 42 | 43 | public function exists($id) 44 | { 45 | return $this->redis->exists($id); 46 | } 47 | 48 | public function get($id) 49 | { 50 | return $this->redis->get($id); 51 | } 52 | 53 | public function getMulti(array $ids) 54 | { 55 | $pipe = $this->redis->pipeline(); 56 | array_map(array($pipe, 'get'), $ids); 57 | return $pipe->execute(); 58 | } 59 | 60 | public function add($id, $value, $ttl = null) 61 | { 62 | try { 63 | $this->redis->setnx($id, $value); 64 | if ($ttl) { 65 | $this->redis->expire($id, $ttl); 66 | } 67 | return true; 68 | } catch (Predis\PredisException $e) { 69 | return false; 70 | } 71 | } 72 | 73 | public function set($id, $value, $ttl = null) 74 | { 75 | try { 76 | $this->redis->set($id, $value); 77 | if ($ttl) { 78 | $this->redis->expire($id, $ttl); 79 | } 80 | return true; 81 | } catch (Predis\PredisException $e) { 82 | return false; 83 | } 84 | } 85 | 86 | public function setMulti(array $items, $ttl = null) 87 | { 88 | $pipe = $this->redis->pipeline(); 89 | foreach ($items as $id => $value) { 90 | $pipe->set($id, $value); 91 | if ($ttl) { 92 | $pipe->expire($id, $ttl); 93 | } 94 | } 95 | $pipe->execute(); 96 | return true; 97 | } 98 | 99 | public function delete($id) 100 | { 101 | return $this->redis->del($id); 102 | } 103 | 104 | public function flushAll() 105 | { 106 | $this->redis->flushdb(); 107 | } 108 | 109 | public function supportsPipelines() 110 | { 111 | return true; 112 | } 113 | 114 | public function createPipeline() 115 | { 116 | return $this->redis->pipeline(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/CacheCache/Backends/Session.php: -------------------------------------------------------------------------------- 1 | cache = &$_SESSION; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CacheCache/Cache.php: -------------------------------------------------------------------------------- 1 | backend = $backend; 50 | $this->namespace = $namespace; 51 | $this->defaultTTL = $defaultTTL; 52 | $this->ttlVariation = $ttlVariation; 53 | } 54 | 55 | /** 56 | * Sets the backend of this cache 57 | * 58 | * @param Backend $backend 59 | */ 60 | public function setBackend(Backend $backend) 61 | { 62 | $this->backend = $backend; 63 | } 64 | 65 | /** 66 | * @return Backend 67 | */ 68 | public function getBackend() 69 | { 70 | return $this->backend; 71 | } 72 | 73 | /** 74 | * Sets the namespace for cache ids 75 | * 76 | * @param string $namespace 77 | */ 78 | public function setNamespace($namespace) 79 | { 80 | $this->namespace = $namespace; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getNamespace() 87 | { 88 | return $this->namespace; 89 | } 90 | 91 | /** 92 | * Default time to live value in seconds for all data modification queries 93 | * 94 | * @param int $ttl 95 | */ 96 | public function setDefaultTTL($ttl) 97 | { 98 | $this->defaultTTL = $ttl; 99 | } 100 | 101 | /** 102 | * @return int 103 | */ 104 | public function getDefaultTTL() 105 | { 106 | return $this->defaultTTL; 107 | } 108 | 109 | /** 110 | * To avoid that all values invalidates at the same time, a small 111 | * variation can be added to TTL values of all data modification queries. 112 | * 113 | * @param int $amplitude Maximum value in seconds the variation can be 114 | */ 115 | public function setTTLVariation($amplitude) 116 | { 117 | $this->ttlVariation = $amplitude; 118 | } 119 | 120 | /** 121 | * @return int 122 | */ 123 | public function getTTLVariation() 124 | { 125 | return $this->ttlVariation; 126 | } 127 | 128 | /** 129 | * Computes the final TTL taking into account the default ttl 130 | * and the ttl variation 131 | * 132 | * @param int $ttl 133 | * @return int 134 | */ 135 | public function computeTTL($ttl = null) 136 | { 137 | $ttl = $ttl ?: $this->defaultTTL; 138 | if ($ttl === null) { 139 | return null; 140 | } 141 | return $ttl + rand(0, $this->ttlVariation); 142 | } 143 | 144 | /** 145 | * Computes and id as it will be inserted into the backend 146 | * (ie. taking into account the namespace) 147 | * 148 | * Any number of parameters can be used with this method. 149 | * They will all be concatenated with the $namespaceSeparator. 150 | * 151 | * @param string $id 152 | * @return string 153 | */ 154 | public function id($id) 155 | { 156 | $parts = array_merge(array($this->namespace), func_get_args()); 157 | return trim(implode(self::$namespaceSeparator, $parts), self::$namespaceSeparator); 158 | } 159 | 160 | /** 161 | * Returns a {@see Cache} object for a sub-namespace. 162 | * 163 | * @param string $namespace 164 | * @param int $defaultTTL 165 | * @return Cache 166 | */ 167 | public function ns($namespace, $defaultTTL = null) 168 | { 169 | $namespace = $this->id($namespace); 170 | $defaultTTL = $defaultTTL ?: $this->defaultTTL; 171 | return new Cache($this->backend, $namespace, $defaultTTL, $this->ttlVariation); 172 | } 173 | 174 | /** 175 | * {@inheritDoc} 176 | */ 177 | public function exists($id) 178 | { 179 | $id = $this->id($id); 180 | return $this->backend->exists($id); 181 | } 182 | 183 | /** 184 | * {@inheritDoc} 185 | */ 186 | public function get($id, $default = null) 187 | { 188 | $id = $this->id($id); 189 | if (($value = $this->backend->get($id)) === null) { 190 | return $default; 191 | } 192 | return $value; 193 | } 194 | 195 | /** 196 | * {@inheritDoc} 197 | */ 198 | public function getMulti(array $ids) 199 | { 200 | $ids = array_map(array($this, 'id'), $ids); 201 | return $this->backend->getMulti($ids); 202 | } 203 | 204 | /** 205 | * {@inheritDoc} 206 | */ 207 | public function add($id, $value, $ttl = null) 208 | { 209 | $id = $this->id($id); 210 | $ttl = $this->computeTTL($ttl); 211 | return $this->backend->add($id, $value, $ttl); 212 | } 213 | 214 | /** 215 | * {@inheritDoc} 216 | */ 217 | public function set($id, $value, $ttl = null) 218 | { 219 | $id = $this->id($id); 220 | $ttl = $this->computeTTL($ttl); 221 | return $this->backend->set($id, $value, $ttl); 222 | } 223 | 224 | /** 225 | * {@inheritDoc} 226 | */ 227 | public function setMulti(array $items, $ttl = null) 228 | { 229 | $ids = array_map(array($this, 'id'), array_keys($items)); 230 | $items = array_combine($ids, array_values($items)); 231 | $ttl = $this->computeTTL($ttl); 232 | return $this->backend->setMulti($items, $ttl); 233 | } 234 | 235 | /** 236 | * {@inheritDoc} 237 | */ 238 | public function delete($id) 239 | { 240 | $id = $this->id($id); 241 | return $this->backend->delete($id); 242 | } 243 | 244 | /** 245 | * {@inheritDoc} 246 | */ 247 | public function flushAll() 248 | { 249 | return $this->backend->flushAll(); 250 | } 251 | 252 | /** 253 | * Returns the value of $id if it exists. Sets $id to $value otherwise. 254 | * 255 | * $value can be a closure that will only be called if $id does not exists. 256 | * It must return the value to be added to the cache. 257 | * 258 | * @param string $id 259 | * @param mixed|Closure $value 260 | * @param int $ttl 261 | * @return mixed 262 | */ 263 | public function getset($id, $value, $ttl = null) 264 | { 265 | if (($v = $this->get($id)) === null) { 266 | if ($value instanceof \Closure) { 267 | $v = $value($this); 268 | } else { 269 | $v = $value; 270 | } 271 | $this->add($id, $v, $ttl); 272 | } 273 | return $v; 274 | } 275 | 276 | /** 277 | * Tries to fetch the $id entry. If it does not exist, returns false 278 | * and saves the $id for a later call to {@see save()}. 279 | * 280 | * Nested calls can be performed. 281 | * 282 | * 283 | * if (!($data = $cache->load('myid'))) { 284 | * // do heavy stuff 285 | * // $data = ... 286 | * $cache->save($data); 287 | * } 288 | * 289 | * 290 | * @param string $id 291 | * @return mixed 292 | */ 293 | public function load($id) 294 | { 295 | if (($value = $this->get($id)) !== null) { 296 | return $value; 297 | } 298 | $this->stack[] = $id; 299 | return false; 300 | } 301 | 302 | /** 303 | * Saves some $data in the cache using the last $id 304 | * provided to the {@see load()} method. 305 | * 306 | * @param mixed $data 307 | * @param int $ttl 308 | */ 309 | public function save($data, $ttl = null) 310 | { 311 | if (empty($this->stack)) { 312 | throw new CacheException("Cache::load() must be called before Cache::save()"); 313 | } 314 | $id = array_pop($this->stack); 315 | return $this->add($id, $data, $ttl); 316 | } 317 | 318 | /** 319 | * Similar to {@see load()} but starts capturing the output if $id is not 320 | * found or echoes (unless $echo is set to false) the retreived value. 321 | * 322 | * Nested calls can be performed. 323 | * 324 | * 325 | * if (!$cache->start('myid')) { 326 | * echo "lots of data"; 327 | * $cache->end(); 328 | * } 329 | * 330 | * 331 | * @param string $id 332 | * @param bool $echo 333 | * @return mixed 334 | */ 335 | public function start($id, $echo = true) 336 | { 337 | if (($output = $this->load($id)) === false) { 338 | ob_start(); 339 | $this->capturing++; 340 | return false; 341 | } 342 | if ($echo) { 343 | echo $output; 344 | } 345 | return $output; 346 | } 347 | 348 | /** 349 | * Similar to {@see save()} but saves the output since the last call 350 | * to {@see start()}. Also echoes the output unless $echo is set to false. 351 | * 352 | * @param int $ttl 353 | * @param bool $echo 354 | * @return string The captured output 355 | */ 356 | public function end($ttl = null, $echo = true) 357 | { 358 | if (!empty($this->stack) && $this->capturing > 0) { 359 | $output = ob_get_clean(); 360 | $this->save($output, $ttl); 361 | $this->capturing--; 362 | if ($echo) { 363 | echo $output; 364 | } 365 | return $output; 366 | } 367 | return false; 368 | } 369 | 370 | /** 371 | * Checks if a capture started by {@see start()} is currently being performed. 372 | * 373 | * @return bool 374 | */ 375 | public function isCapturing() 376 | { 377 | return $this->capturing > 0; 378 | } 379 | 380 | /** 381 | * Cancels the last call to either {@see load()} or {@see start()}. Further 382 | * calls to {@see save()} or {@see end()} will be ignored. 383 | */ 384 | public function cancel() 385 | { 386 | if (!empty($this->stack)) { 387 | array_pop($this->stack); 388 | if ($this->capturing > 0) { 389 | $this->capturing--; 390 | ob_end_flush(); 391 | } 392 | } 393 | } 394 | 395 | /** 396 | * Similar to {@see start()} followed by {@see end()} but will capture 397 | * all the output done while $callback is executed. 398 | * 399 | * 400 | * $cache->capture('myid', function() { 401 | * echo "lots of data"; 402 | * }) 403 | * 404 | * 405 | * @param string $id 406 | * @param callback $callback 407 | * @param int $ttl 408 | * @param bool $echo 409 | * @return string 410 | */ 411 | public function capture($id, $callback, $ttl = null, $echo = true) 412 | { 413 | if (($output = $this->start($id, $echo)) === false) { 414 | call_user_func($callback, $this); 415 | return $this->end($ttl, $echo); 416 | } 417 | return $output; 418 | } 419 | 420 | /** 421 | * Captures the whole output of the script until it ends. 422 | * 423 | * If no $id is specified, it will be computed from a combination 424 | * of the $_SERVER['REQUEST_URI'] and the $_REQUEST variables. 425 | * 426 | * If $id is found, the script will exit unless $exit is set to false. 427 | * 428 | * @param string $id 429 | * @param int $ttl 430 | * @param bool $exit 431 | * @return bool 432 | */ 433 | public function capturePage($id = null, $ttl = null, $exit = true) 434 | { 435 | if ($id === null) { 436 | $id = md5(serialize($_SERVER['REQUEST_URI']) . serialize($_REQUEST)); 437 | } 438 | 439 | if ($this->start($id)) { 440 | if ($exit) { 441 | exit; 442 | } 443 | return true; 444 | } 445 | $self = $this; 446 | register_shutdown_function(function() use ($self, $ttl) { $self->end($ttl); }); 447 | return false; 448 | } 449 | 450 | /** 451 | * Works the same way as {@see call_user_func_array()} but calls are cached. 452 | * 453 | * The cache id will be computed from the $callback and the $args. 454 | * 455 | * 456 | * function do_heavy_computing($data) { } 457 | * $result = $cache->call('do_heavy_computing', array($data)); 458 | * 459 | * 460 | * @param callback $callback Any callbacks unless it is a closure, in this case use {@see getset()} 461 | * @param array $args 462 | * @param int $ttl 463 | * @return mixed 464 | */ 465 | public function call($callback, array $args, $ttl = null) 466 | { 467 | $id = md5(serialize($callback) . serialize($args)); 468 | if (($value = $this->get($id)) === null) { 469 | $value = call_user_func_array($callback, $args); 470 | $this->add($id, $value, $ttl); 471 | } 472 | return $value; 473 | } 474 | 475 | /** 476 | * Wraps an object in {@see ObjectWrapper}. 477 | * 478 | * @see ObjectWrapper 479 | * @param object $object 480 | * @param string $id If null, the object's class name will be used 481 | * @param int $ttl 482 | * @return ObjectWrapper 483 | */ 484 | public function wrap($object, $id = null, $ttl = null) 485 | { 486 | $id = $id ?: get_class($object); 487 | return new ObjectWrapper($object, $this->ns($id, $ttl)); 488 | } 489 | 490 | /** 491 | * {@inheritDoc} 492 | */ 493 | public function supportsPipelines() 494 | { 495 | return true; 496 | } 497 | 498 | /** 499 | * Creates a pipeline for batching operations 500 | * 501 | * If the backend does not support pipelines, the 502 | * generic {@see Pipeline} implementation will be used. 503 | * 504 | * @return Pipeline 505 | */ 506 | public function createPipeline() 507 | { 508 | if ($this->backend->supportsPipelines()) { 509 | return $this->backend->createPipeline(); 510 | } 511 | return new Pipeline($this); 512 | } 513 | 514 | /** 515 | * Creates a pipeline, executes the callback which should use 516 | * the provided pipeline object as its only argument without 517 | * executing it. The pipeline will the be executed and its 518 | * results will be returned. 519 | * 520 | * If no callback is specified, the pipeline object will be 521 | * returned without being executed. 522 | * 523 | * 524 | * $results = $cache->pipeline(function($pipe) { 525 | * $pipe->set('id1', 'value1'); 526 | * $pipe->set('id2', 'value2'); 527 | * }) 528 | * 529 | * 530 | * @param callback $callback 531 | * @return mixed 532 | */ 533 | public function pipeline($callback = null) 534 | { 535 | $pipe = $this->createPipeline(); 536 | if ($callback === null) { 537 | return $pipe; 538 | } 539 | call_user_func($callback, $pipe); 540 | return $pipe->execute(); 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /src/CacheCache/CacheException.php: -------------------------------------------------------------------------------- 1 | null, 31 | 'backend_args' => null, 32 | 'namespace' => '', 33 | 'ttl' => null, 34 | 'variation' => 0 35 | ); 36 | 37 | /** @var array */ 38 | private static $caches = array(); 39 | 40 | /** 41 | * Setups the cache manager. 42 | * 43 | * If $caches is the class name of a backend, a {@see Backend} instance, 44 | * a {@see Cache} instance will be created under the default name. 45 | * 46 | * $caches can also be an array to define multiple cache instances an once. 47 | * Keys will be used as cache names and values must be compatible with the 48 | * {@see factory()} method $options argument. 49 | * 50 | * 51 | * CacheManager::setup(array( 52 | * 'default' => 'CacheCache\Backend\File' 53 | * )); 54 | * 55 | * 56 | * If $logger is not null, all Backend instances will be wrapped in a 57 | * {@see LoggingBackend} object. 58 | * 59 | * @see factory() 60 | * @param array $caches 61 | * @param Logger $logger 62 | * @param int $logLevel 63 | */ 64 | public static function setup($caches, Logger $logger = null, $logLevel = null) 65 | { 66 | if (!is_array($caches)) { 67 | $caches = array(self::_DEFAULT => array('backend' => $caches)); 68 | } 69 | 70 | self::$logger = $logger; 71 | self::$logLevel = $logLevel; 72 | 73 | foreach ($caches as $name => $options) { 74 | self::$caches[$name] = self::factory($options); 75 | } 76 | } 77 | 78 | /** 79 | * Creates a {@see Cache} object 80 | * 81 | * $options can either be the class name of a backend, a {@see Backend} 82 | * instance or an array. 83 | * 84 | * Possible array values: 85 | * - backend: backend class name or {@see Backend} instance 86 | * - backend_args: an array of constructor arguments for the backend 87 | * - namespace 88 | * - ttl 89 | * - variation 90 | * 91 | * Default values for these options can be defined in the $defaults static 92 | * property. 93 | * 94 | * @param array $options 95 | * @return Cache 96 | */ 97 | public static function factory($options) 98 | { 99 | if (is_string($options) || $options instanceof Backend) { 100 | $options = array('backend' => $options); 101 | } else if (!is_array($options)) { 102 | throw new CacheException("Options argument in CacheManager::factory() must be an array"); 103 | } 104 | 105 | $options = array_merge(self::$defaults, $options); 106 | if (!isset($options['backend'])) { 107 | throw new CacheException("No backend specified in options array for CacheManager::factory()"); 108 | } 109 | 110 | $backend = $options['backend']; 111 | if (is_string($backend)) { 112 | if (isset($options['backend_args'])) { 113 | $backendClass = new \ReflectionClass($backend); 114 | $backend = $backendClass->newInstanceArgs($options['backend_args']); 115 | } else { 116 | $backend = new $backend(); 117 | } 118 | } 119 | 120 | if (self::$logger !== null) { 121 | $backend = new LoggingBackend($backend, self::$logger, self::$logLevel); 122 | } 123 | 124 | $cache = new Cache($backend, $options['namespace'], $options['ttl'], $options['variation']); 125 | return $cache; 126 | } 127 | 128 | /** 129 | * Makes a {@see Cache} instance available through $name 130 | * 131 | * @param string $name 132 | * @param Cache $cache 133 | */ 134 | public static function set($name, Cache $cache) 135 | { 136 | self::$caches[$name] = $cache; 137 | } 138 | 139 | /** 140 | * Returns the {@see Cache} instance under $name 141 | * 142 | * @param string $name If null will used the instance named CacheManager::_DEFAULT 143 | * @return Cache 144 | */ 145 | public static function get($name = null) 146 | { 147 | $name = $name ?: self::_DEFAULT; 148 | if (!isset(self::$caches[$name])) { 149 | throw new CacheException("Cache '$name' not found"); 150 | } 151 | return self::$caches[$name]; 152 | } 153 | 154 | /** 155 | * Shorcut to self::get()->ns() 156 | * 157 | * @see Cache::ns() 158 | * @param string $namespace 159 | * @param int $defaultTTL 160 | * @return Cache 161 | */ 162 | public static function ns($namespace, $defaultTTL = null) 163 | { 164 | return self::get()->ns($namespace, $defaultTTL); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/CacheCache/LoggingBackend.php: -------------------------------------------------------------------------------- 1 | backend = $backend; 38 | $this->logger = $logger; 39 | $this->logLevel = $logLevel ?: Logger::DEBUG; 40 | } 41 | 42 | /** 43 | * @return Backend 44 | */ 45 | public function getBackend() 46 | { 47 | return $this->backend; 48 | } 49 | 50 | /** 51 | * @param Logger $logger 52 | */ 53 | public function setLogger(Logger $logger) 54 | { 55 | $this->logger = $logger; 56 | } 57 | 58 | /** 59 | * @return Logger 60 | */ 61 | public function getLogger() 62 | { 63 | return $this->logger; 64 | } 65 | 66 | /** 67 | * @param int $level 68 | */ 69 | public function setLogLevel($level) 70 | { 71 | $this->logLevel = $level; 72 | } 73 | 74 | /** 75 | * @return int 76 | */ 77 | public function getLogLevel() 78 | { 79 | return $this->logLevel; 80 | } 81 | 82 | /** 83 | * Logs an operation 84 | * 85 | * @param string $operation 86 | * @param string|array $id 87 | * @param int $ttl 88 | * @param bool $hit 89 | */ 90 | protected function log($operation, $id = null, $ttl = null, $hit = null) 91 | { 92 | $message = strtoupper($operation); 93 | if ($id !== null) { 94 | $id = implode(', ', (array) $id); 95 | if ($ttl !== null) { 96 | $message = sprintf('%s(%s, ttl=%s)', $message, $id, $ttl); 97 | } else { 98 | $message = sprintf('%s(%s)', $message, $id); 99 | } 100 | } 101 | if ($hit !== null) { 102 | $message .= ' = ' . ($hit ? 'HIT' : 'MISS'); 103 | } 104 | $this->logger->addRecord($this->logLevel, $message); 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | */ 110 | public function exists($id) 111 | { 112 | $exists = $this->backend->exists($id); 113 | $this->log("exists", $id, null, $exists); 114 | return $exists; 115 | } 116 | 117 | /** 118 | * {@inheritDoc} 119 | */ 120 | public function get($id) 121 | { 122 | $value = $this->backend->get($id); 123 | $this->log("get", $id, null, $value !== null); 124 | return $value; 125 | } 126 | 127 | /** 128 | * {@inheritDoc} 129 | */ 130 | public function getMulti(array $ids) 131 | { 132 | $values = $this->backend->getMulti($ids); 133 | $this->log("getMulti", $ids, null, $values !== null); 134 | return $values; 135 | } 136 | 137 | /** 138 | * {@inheritDoc} 139 | */ 140 | public function add($id, $value, $ttl = null) 141 | { 142 | $success = $this->backend->add($id, $value, $ttl); 143 | $this->log('add', $id, $ttl); 144 | return $success; 145 | } 146 | 147 | /** 148 | * {@inheritDoc} 149 | */ 150 | public function set($id, $value, $ttl = null) 151 | { 152 | $success = $this->backend->set($id, $value, $ttl); 153 | $this->log('set', $id, $ttl); 154 | return $success; 155 | } 156 | 157 | /** 158 | * {@inheritDoc} 159 | */ 160 | public function setMulti(array $items, $ttl = null) 161 | { 162 | $success = $this->backend->setMulti($items, $ttl); 163 | $this->log('setMulti', array_keys($items), $ttl); 164 | return $success; 165 | } 166 | 167 | /** 168 | * {@inheritDoc} 169 | */ 170 | public function delete($id) 171 | { 172 | $success = $this->backend->delete($id); 173 | $this->log('delete', $id); 174 | return $success; 175 | } 176 | 177 | /** 178 | * {@inheritDoc} 179 | */ 180 | public function flushAll() 181 | { 182 | $success = $this->backend->flushAll(); 183 | $this->log('flushAll'); 184 | return $success; 185 | } 186 | 187 | /** 188 | * {@inheritDoc} 189 | */ 190 | public function supportsPipelines() 191 | { 192 | return $this->backend->supportsPipelines(); 193 | } 194 | 195 | /** 196 | * {@inheritDoc} 197 | */ 198 | public function createPipeline() 199 | { 200 | return $this->backend->createPipeline(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/CacheCache/MultiCache.php: -------------------------------------------------------------------------------- 1 | backends = $backends; 40 | $this->namespace = $namespace; 41 | $this->defaultTTL = $defaultTTL; 42 | $this->ttlVariation = $ttlVariation; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | public function getBackends() 49 | { 50 | return $this->backends; 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public function exists($id) 57 | { 58 | $id = $this->id($id); 59 | foreach ($this->backends as $backend) { 60 | if ($backend->exists($id)) { 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | public function get($id, $default = null) 71 | { 72 | $id = $this->id($id); 73 | foreach ($this->backends as $backend) { 74 | if (($value = $backend->get($id)) !== null) { 75 | return $value; 76 | } 77 | } 78 | return $default; 79 | } 80 | 81 | /** 82 | * {@inheritDoc} 83 | */ 84 | public function getMulti(array $ids) 85 | { 86 | $ids = array_map(array($this, 'id'), $ids); 87 | foreach ($this->backends as $backend) { 88 | if (($values = $backend->getMulti($ids)) !== null) { 89 | return $values; 90 | } 91 | } 92 | return null; 93 | } 94 | 95 | /** 96 | * {@inheritDoc} 97 | */ 98 | public function add($id, $value, $ttl = null) 99 | { 100 | $id = $this->id($id); 101 | $ttl = $this->computeTTL($ttl); 102 | foreach ($this->backends as $backend) { 103 | $backend->add($id, $value, $ttl); 104 | } 105 | } 106 | 107 | /** 108 | * {@inheritDoc} 109 | */ 110 | public function set($id, $value, $ttl = null) 111 | { 112 | $id = $this->id($id); 113 | $ttl = $this->computeTTL($ttl); 114 | foreach ($this->backends as $backend) { 115 | $backend->set($id, $value, $ttl); 116 | } 117 | } 118 | 119 | /** 120 | * {@inheritDoc} 121 | */ 122 | public function setMulti(array $items, $ttl = null) 123 | { 124 | $ids = array_map(array($this, 'id'), array_keys($items)); 125 | $items = array_combine($ids, array_values($items)); 126 | $ttl = $this->computeTTL($ttl); 127 | foreach ($this->backends as $backend) { 128 | $backend->setMulti($items, $ttl); 129 | } 130 | } 131 | 132 | /** 133 | * {@inheritDoc} 134 | */ 135 | public function delete($id) 136 | { 137 | $id = $this->id($id); 138 | foreach ($this->backends as $backend) { 139 | $backend->delete($id); 140 | } 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | public function flushAll() 147 | { 148 | foreach ($this->backends as $backend) { 149 | $backend->flushAll(); 150 | } 151 | } 152 | 153 | /** 154 | * {@inheritDoc} 155 | */ 156 | public function createPipeline() 157 | { 158 | return new Pipeline($this); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/CacheCache/ObjectWrapper.php: -------------------------------------------------------------------------------- 1 | 21 | * $obj = new ObjectWrapper(new MyClass(), $backend); 22 | * $data = $obj->myMethod(); 23 | * 24 | */ 25 | class ObjectWrapper 26 | { 27 | /** @var Backend */ 28 | protected $backend; 29 | 30 | /** @var object */ 31 | protected $object; 32 | 33 | /** 34 | * @param object $object 35 | * @param Backend $backend 36 | */ 37 | public function __construct($object, Backend $backend) 38 | { 39 | $this->object = $object; 40 | $this->backend = $backend; 41 | } 42 | 43 | public function __get($name) 44 | { 45 | return $this->object->$name; 46 | } 47 | 48 | public function __set($name, $value) 49 | { 50 | $this->object->$name = $value; 51 | } 52 | 53 | public function __isset($name) 54 | { 55 | return isset($this->object->$name); 56 | } 57 | 58 | public function __unset($name) 59 | { 60 | unset($this->object->$name); 61 | } 62 | 63 | public function __toString() 64 | { 65 | return $this->__call('__toString', func_get_args()); 66 | } 67 | 68 | public function __invoke() 69 | { 70 | return $this->__call('__invoke', func_get_args()); 71 | } 72 | 73 | public function __call($method, $args) 74 | { 75 | $id = md5($method . serialize($args) . serialize(get_object_vars($this->object))); 76 | if (($value = $this->backend->get($id)) === null) { 77 | $value = call_user_func_array(array($this->object, $method), $args); 78 | $this->backend->add($id, $value); 79 | } 80 | return $value; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/CacheCache/Pipeline.php: -------------------------------------------------------------------------------- 1 | 18 | * $pipe = new Pipeline($backend); 19 | * $pipe->set('id1', 'value1'); 20 | * $pipe->set('id2', 'value2'); 21 | * $pipe->get('id1'); 22 | * $pipe->get('id2'); 23 | * $results = $pipe->execute(); 24 | * 25 | * // is equivalent to: 26 | * 27 | * $setResults = $backend->setMulti(array('id1' => 'value1', 'id2' => 'value2')); 28 | * $getResults = $backend->getMulti(array('id1', 'id2')); 29 | * $results = array_merge($setResults, $getResults); 30 | * 31 | */ 32 | class Pipeline 33 | { 34 | /** @var Backend */ 35 | protected $backend; 36 | 37 | /** @var array */ 38 | protected $commands = array(); 39 | 40 | /** @var int */ 41 | protected $ttl = null; 42 | 43 | /** 44 | * @param Backend $backend 45 | */ 46 | public function __construct(Backend $backend) 47 | { 48 | $this->backend = $backend; 49 | } 50 | 51 | /** 52 | * Registers a GET command 53 | * 54 | * @param string $id 55 | */ 56 | public function get($id) 57 | { 58 | $this->commands[] = array('get', $id); 59 | } 60 | 61 | /** 62 | * Registers a SET command 63 | * 64 | * @param string $id 65 | * @param mixed $value 66 | */ 67 | public function set($id, $value) 68 | { 69 | $this->commands[] = array('set', $id, $value); 70 | } 71 | 72 | /** 73 | * Sets the ttl for all SET commands 74 | * 75 | * @param int $ttl 76 | */ 77 | public function ttl($ttl = null) 78 | { 79 | $this->ttl = $ttl; 80 | } 81 | 82 | /** 83 | * Executes the pipeline and returns results of individual commands 84 | * as an array. 85 | * 86 | * @return array 87 | */ 88 | public function execute() 89 | { 90 | $groups = array(); 91 | $results = array(); 92 | $currentOperation = null; 93 | $currentGroup = array(); 94 | 95 | foreach ($this->commands as $command) { 96 | if ($currentOperation !== $command[0]) { 97 | $groups[] = array($currentOperation, $currentGroup); 98 | $currentOperation = $command[0]; 99 | $currentGroup = array(); 100 | } 101 | if ($currentOperation === 'get') { 102 | $currentGroup[] = $command[1]; 103 | } else { 104 | $currentGroup[$command[1]] = $command[2]; 105 | } 106 | } 107 | $groups[] = array($currentOperation, $currentGroup); 108 | array_shift($groups); 109 | 110 | foreach ($groups as $group) { 111 | list($op, $args) = $group; 112 | if ($op === 'set') { 113 | $result = $this->backend->setMulti($args, $this->ttl); 114 | $results = array_merge($results, array_fill(0, count($args), $result)); 115 | } else { 116 | $results = array_merge($results, $this->backend->getMulti($args)); 117 | } 118 | } 119 | 120 | $this->commands = array(); 121 | return $results; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/CacheCache/Tests/CacheTest.php: -------------------------------------------------------------------------------- 1 | cache = new Cache(new Backends\Memory()); 13 | } 14 | 15 | public function testComputeTTL() 16 | { 17 | $this->cache->setTTLVariation(10); 18 | $ttl = $this->cache->computeTTL(10); 19 | $this->assertInternalType('int', $ttl); 20 | $this->assertGreaterThanOrEqual(10, $ttl); 21 | $this->assertLessThanOrEqual(20, $ttl); 22 | } 23 | 24 | public function testId() 25 | { 26 | $this->assertEquals('foo', $this->cache->id('foo')); 27 | $this->assertEquals('foo:bar', $this->cache->id('foo', 'bar')); 28 | Cache::$namespaceSeparator = '-'; 29 | $this->assertEquals('foo-bar', $this->cache->id('foo', 'bar')); 30 | Cache::$namespaceSeparator = ':'; 31 | } 32 | 33 | public function testNs() 34 | { 35 | $cache = $this->cache->ns('foo'); 36 | $this->assertEquals($this->cache->getBackend(), $cache->getBackend()); 37 | $this->assertEquals('foo:bar', $cache->id('bar')); 38 | } 39 | 40 | public function testExists() 41 | { 42 | $this->assertFalse($this->cache->exists('foo')); 43 | $this->cache->set('foo', 'bar'); 44 | $this->assertTrue($this->cache->exists('foo')); 45 | $this->cache->set('foo', false); 46 | $this->assertTrue($this->cache->exists('foo')); 47 | $this->cache->delete('foo'); 48 | $this->assertFalse($this->cache->exists('foo')); 49 | } 50 | 51 | public function testGet() 52 | { 53 | $this->assertNull($this->cache->get('foo')); 54 | $this->assertEquals('default', $this->cache->get('foo', 'default')); 55 | $this->cache->set('foo', 'bar'); 56 | $this->assertEquals('bar', $this->cache->get('foo')); 57 | } 58 | 59 | public function testGetMulti() 60 | { 61 | $r = $this->cache->getMulti(array('foo', 'bar')); 62 | $this->assertCount(2, $r); 63 | $this->assertNull($r[0]); 64 | $this->assertNull($r[1]); 65 | 66 | $this->cache->set('foo', 'bar'); 67 | $this->cache->set('bar', 'foo'); 68 | $r = $this->cache->getMulti(array('foo', 'bar')); 69 | $this->assertCount(2, $r); 70 | $this->assertEquals('bar', $r[0]); 71 | $this->assertEquals('foo', $r[1]); 72 | } 73 | 74 | public function testAdd() 75 | { 76 | $this->cache->add('foo', 'bar'); 77 | $this->assertEquals('bar', $this->cache->get('foo')); 78 | $this->cache->add('foo', 'baz'); 79 | $this->assertEquals('bar', $this->cache->get('foo')); 80 | } 81 | 82 | public function testSet() 83 | { 84 | $this->cache->set('foo', 'bar'); 85 | $this->assertEquals('bar', $this->cache->get('foo')); 86 | $this->cache->set('foo', 'baz'); 87 | $this->assertEquals('baz', $this->cache->get('foo')); 88 | } 89 | 90 | public function testSetTTL() 91 | { 92 | $this->cache->set('foo', 'bar', 1); 93 | $this->assertEquals('bar', $this->cache->get('foo')); 94 | sleep(2); 95 | $this->assertFalse($this->cache->exists('foo')); 96 | } 97 | 98 | public function testSetMulti() 99 | { 100 | $this->cache->setMulti(array( 101 | 'foo' => 'bar', 102 | 'bar' => 'foo' 103 | )); 104 | 105 | $this->assertEquals('bar', $this->cache->get('foo')); 106 | $this->assertEquals('foo', $this->cache->get('bar')); 107 | } 108 | 109 | public function testDelete() 110 | { 111 | $this->cache->set('foo', 'bar'); 112 | $this->assertEquals('bar', $this->cache->get('foo')); 113 | $this->cache->delete('foo'); 114 | $this->assertFalse($this->cache->exists('foo')); 115 | } 116 | 117 | public function testFlushAll() 118 | { 119 | $this->cache->setMulti(array( 120 | 'foo' => 'bar', 121 | 'bar' => 'foo' 122 | )); 123 | $this->assertCount(2, $this->cache->getBackend()->toArray()); 124 | $this->cache->flushAll(); 125 | $this->assertEmpty($this->cache->getBackend()->toArray()); 126 | } 127 | 128 | public function testGetSet() 129 | { 130 | $closure = function() { 131 | return 'bar'; 132 | }; 133 | 134 | $this->assertEquals('bar', $this->cache->getset('foo', $closure)); 135 | $this->assertEquals('bar', $this->cache->get('foo')); 136 | $this->cache->set('foo', 'baz'); 137 | $this->assertEquals('baz', $this->cache->getset('foo', $closure)); 138 | $this->assertEquals('baz', $this->cache->get('foo')); 139 | } 140 | 141 | public function testLoadSave() 142 | { 143 | $this->assertFalse($this->cache->load('foo')); 144 | $this->cache->save('bar'); 145 | $this->assertEquals('bar', $this->cache->get('foo')); 146 | $this->assertEquals('bar', $this->cache->load('foo')); 147 | } 148 | 149 | public function testStartEnd() 150 | { 151 | $this->assertFalse($this->cache->start('foo')); 152 | echo 'bar'; 153 | $this->assertTrue($this->cache->isCapturing()); 154 | $this->cache->end(); 155 | $this->expectOutputString('bar'); 156 | $this->assertEquals('bar', $this->cache->get('foo')); 157 | $this->assertEquals('bar', $this->cache->start('foo', false)); 158 | } 159 | 160 | /** 161 | * @expectedException CacheCache\CacheException 162 | * @expectedExceptionMessage Cache::load() must be called before Cache::save() 163 | */ 164 | public function testCancel() 165 | { 166 | $this->assertFalse($this->cache->load('foo')); 167 | $this->cache->cancel(); 168 | $this->cache->save('bar'); 169 | } 170 | 171 | public function testCapture() 172 | { 173 | $closure = function() { 174 | echo 'bar'; 175 | }; 176 | $this->cache->capture('foo', $closure); 177 | $this->expectOutputString('bar'); 178 | $this->assertEquals('bar', $this->cache->get('foo')); 179 | $this->assertEquals('bar', $this->cache->capture('foo', $closure, null, false)); 180 | } 181 | 182 | public function testCall() 183 | { 184 | $start = time(); 185 | $this->cache->call('sleep', array(1)); 186 | $this->assertGreaterThanOrEqual(1, time() - $start); 187 | $start = microtime(true); 188 | $this->cache->call('sleep', array(1)); 189 | $this->assertLessThan(500, microtime(true)- $start); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/CacheCache/Tests/CacheTestCase.php: -------------------------------------------------------------------------------- 1 | cache = new Cache(new Backends\Memory()); 22 | } 23 | 24 | public function testProperties() 25 | { 26 | $obj = $this->cache->wrap(new TestObject()); 27 | $this->assertEquals('foobar', $obj->foobar); 28 | } 29 | 30 | public function testCalls() 31 | { 32 | $obj = $this->cache->wrap(new TestObject()); 33 | 34 | $start = time(); 35 | $obj->longOp(); 36 | $this->assertGreaterThanOrEqual(1, time() - $start); 37 | 38 | $start = microtime(true); 39 | $obj->longOp(); 40 | $this->assertLessThan(500, microtime(true) - $start); 41 | 42 | $start = time(); 43 | $obj->longOp(2); 44 | $this->assertGreaterThanOrEqual(2, time() - $start); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |