├── .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 | [](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 |