├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── composer.json
├── src
├── AbstractCache.php
├── AbstractPdo.php
├── Adapter.php
├── Apc.php
├── Apcu.php
├── Directory.php
├── Exception.php
├── Factory.php
├── Files.php
├── Indexer
│ ├── AbstractIndexer.php
│ ├── Adapter.php
│ └── MemcachedIndexer.php
├── Memcached.php
├── Mongo.php
├── Mongo
│ ├── CollectionAdapter.php
│ └── DatabaseAdapter.php
├── Pdo
│ ├── Mysql.php
│ ├── Pgsql.php
│ ├── Sql1999.php
│ └── Sqlite.php
├── PsrCache
│ ├── InvalidArgumentException.php
│ ├── Item.php
│ ├── Pool.php
│ ├── TaggableItem.php
│ └── TaggablePool.php
├── Redis.php
├── Runtime.php
└── Serializer
│ ├── Adapter.php
│ ├── Igbinary.php
│ ├── Json.php
│ ├── Msgpack.php
│ ├── None.php
│ ├── Php.php
│ └── Stringset.php
└── tests
├── AbstractCacheTest.php
├── ApcTest.php
├── ApcuTest.php
├── DirectoryTest.php
├── FactoryTest.php
├── FilesTest.php
├── GenericTestCase.php
├── Indexer
├── ApcIndexerTest_OFF.php
├── GenericIndexerTestCase.php
└── MemcachedIndexerTest.php
├── MemcachedTest.php
├── MongoTest.php
├── PdoTest.php
├── PsrCache
├── ItemTest.php
├── PoolTest.php
├── TaggableItemTest.php
└── TaggablePoolTest.php
├── RedisTest.php
├── RuntimeTest.php
├── Serializer
├── IgbinaryTest.php
├── JsonTest.php
├── MsgpackTest.php
├── NoneTest.php
├── PhpTest.php
├── StringsetTest.php
└── TestCase.php
├── TestCase.php
└── bin
└── travis-init.sh
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # APIx Cache changelog
2 |
3 | #### Version 1.3.6 (XX-Dec-2020)
4 | - On PHP >= 7.3, if the key passed to `loadKey()` isn't in the cache a
5 | notice is generated because value returned from `PDO::fetch()` is false
6 | but it is treated as an array (see #40 and PR #44 by @BlairCooper).
7 |
8 | #### Version 1.3.5 (11-Jun-2020)
9 | - Improved the `flush` method for the SQL backends (see #40 and PR #39 by @BlairCooper).
10 |
11 | #### Version 1.3.4 (30-Oct-2019)
12 | - Fixed Memcached unit tests for PHP 7.x (PR #39 by @BlairCooper).
13 | - Fixed `Memcached.php::setSerializer` for PHP 7.3 compatibility (PR #37 by @BlairCooper & PR #38 by @mgmbh).
14 | - Fixed Travis builds with PHP 5.* (PR #37 by @BlairCooper).
15 | - Fixed Travis on PHP 5.4 and 5.5 cannot run on Xenial (PR #37 by @BlairCooper).
16 | - Fixed typo in composer suggested package (PR #35 by @Great-Antique).
17 | - Added cas_token to `getTtl()` method (PR #34 contrib by @BlairCooper).
18 | - Added check of "expire" when entry is read from a file (PR #33 contrib by @dimasikturbo).
19 |
20 | #### Version 1.3.3 (18-May-2018)
21 | - Modified `Redis::flush` to delete all keys only from the current DB, instead of deleting all keys from the server (PR #31 contrib by @alexpica).
22 | - Fixes to handle changes with phpredis >= 4, cast return value to Boolean.
23 | - Fix `APCu` adapter to use either `\APCUIterator` or `\APCIterator`.
24 | - Modified Travis config to handle PHP 5.3 and Precise distro.
25 |
26 | #### Version 1.3.2 (19-Jul-2017)
27 | - Added a dedicated `APCu` backend (+ relevant tests) as the extension no longer ship with the backward compatibility module 'apcu-bc’ (see #29).
28 |
29 | #### Version 1.3.1 (19-Jun-2017)
30 | - Fix a MySQL issue where same key/value returned 0 number of updated rows and triggered "SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry" (PR #28 contrib by @dimasikturbo).
31 |
32 | #### Version 1.3.0 (2-May-2017)
33 | - Fix a PSR-6 expiration issue.
34 | - Added `PsrCache\Item::__toString` method to simplify cached value output.
35 | - Added `PsrCache\Pool::__destruct()` method to (garbage collect) magically commit all deferred cached items.
36 | - Removed all deprecated methods from `PsrCache`.
37 | - Various fixes, more unit-tests and some cleanup.
38 |
39 | #### Version 1.2.9 (5-Jan-2017)
40 | - Fix `Files::clean` and `Directory::clean` return to early if failing to find a tag (PR #17 by @melloc01 + relevant tests PR #24).
41 | - Fix `Files::flush(true)` the implementation to flush all was missing (PR #25 contrib by @alexpica).
42 | - Updated to allow patches from php-fig/cache (PR #26 contrib by @vaibhavpandeyvpz).
43 | - Fix to a deprecated method `PsrCache::setExpiration` (PR #27 contrib by @damianopetrungaro).
44 |
45 | #### Version 1.2.8 (28-Oct-2016)
46 | - Added new `mongodb` extension for PHP 5.4 and higher (contrib by @dimasikturbo) which also supports HHVM 3.9 and higher. The legacy `mongo` extension is still provided for PHP 5.6 and lower.
47 | - Set Travis to skip `mongodb` on HHVM (compilation issue).
48 | - Fix array serialisation of nested keys with Mongo (contrib by @dimasikturbo).
49 |
50 | #### Version 1.2.7 (20-July-2016)
51 | - Fix the HHVM issues.
52 | - Fix APC/APCu for both PHP7 and HHVM.
53 | - Updated `.travis` (optimisations).
54 | - Added `msgpack` to Redis, Memcached and to all the PDO backends.
55 | - Added 'auto' and 'json_array' to Memcached.
56 | - Changed Memcached default serializer to `auto`.
57 | - Updated `README.md`.
58 | - Added some additional unit-tests.
59 | - Fix issue #15 "Files cache not correctly handling EOL on Windows" (thanks goes to @davybatsalle).
60 |
61 | #### Version 1.2.6 (4-July-2016)
62 | - Fix issue #13 "TaggablePool and Pool overrides prefix_key and prefix_tag options with hardcoded value" (thanks goes to @alexpica).
63 | - Fix PHP 5.3, using `array()` instead of the short array syntax `[]`.
64 | - Marcked as depreciated `isSerialized()` and `testIsSerialized()`.
65 | - Added `msgpack` serializer.
66 | - Set Travis to skip `Memcached` on PHP 7.0 (not yet officially supported).
67 | - Added additional unit-tests, aiming for 100% code coverage.
68 |
69 | #### Version 1.2.5 (20-Jun-2016)
70 | - Fix issue #12 by adding `Files` and `Directory` backends to the Factory class (thanks goes to @alexpica).
71 | - Added some additional Factory tests.
72 |
73 | #### Version 1.2.4 (6-Jan-2016)
74 | - Updated PSR-Cache (Draft) to PSR-6 (Accepted).
75 | - Marked as deprecated: `PsrCache::setExpiration`, `PsrCache::isRegenerating`, `PsrCache::exists`.
76 | - Added additional unit tests to cover PSR-6.
77 | - Updated `composer.json`.
78 | - Updated `README.md`.
79 | - Updated `.gitignore`.
80 | - Added file locking option to the filesystem backends (contrib by @MacFJA).
81 |
82 | #### Version 1.2.3 (5-Jan-2016)
83 | - Fix APCu versions (contrib by @mimmi20).
84 | - Added `Files` and `Directory` backends (contrib by @MacFJA).
85 | - Updated `README.md`.
86 |
87 | #### Version 1.2.2 (1-Sept-2015)
88 | - Added a `CHANGELOG.md` file.
89 | - Updated PHPUnit to 4.8 version.
90 | - Dropped (partially) PHP 5.3 support - Memcached seems to be broken.
91 | - Dropped PEAR support.
92 | - Refactored `.travis.yml` tests.
93 | - Made Travis faster (using Docker containers and skipping allowable failures).
94 | - Added support to PHP 7.0.
95 |
96 | #### Version 1.2.1 (4-Oct-2014)
97 | - Added setOption().
98 | - Updated `composer.json`.
99 | - Updated `README.md`.
100 | - Added Scrutinizer checks.
101 | - Merged Scrutinizer Auto-Fixes.
102 | - Various minor changes.
103 |
104 | #### Version 1.2.0 (19-Sept-2014)
105 | - Added preflight option to PDO backends.
106 | - Added PSR-6 Cache support as a factory class.
107 | - Added [Coverall](https://coveralls.io/github/frqnck/apix-cache) support.
108 | - Updated `README.md`.
109 | - Added APCu support.
110 | - Added PHP 5.6 and HHVM support.
111 | - Updated the `README.md`.
112 |
113 | #### Version 1.1.0 (30-Jan-2013)
114 | - Added `--prefer-source` (recommended by @seldaek).
115 | - Updated the `README.md`.
116 | - Added some unit tests.
117 | - Added JSON support to Redis.
118 |
119 | #### Version 1.0.5 (23-Jan-2013)
120 | - Added `loadKey()`, `loadTag()` and removed `load()`.
121 |
122 | #### Version 1.0.4 (22-Jan-2013)
123 | - Added dedicated SQL1999 class.
124 | - Fixed PDO and SQL definitions.
125 | - Fixed `.travis.yml`.
126 |
127 | #### Version 1.0.3 (20-Jan-2013)
128 | - Added some aditional tests.
129 | - Refactored Serialisers.
130 |
131 | #### Version 1.0.2 (20-Jan-2013)
132 | - Added MongoDB implementation.
133 | - Various fixes.
134 |
135 | #### Version 1.0.1 (15-Jan-2013)
136 | - Fixed test for Redis with igBinary.
137 | - Added APC and PhpRedis environments.
138 | - Added PHP 5.5 support.
139 | - Fixed `.travis.yml`.
140 | - Added additional tests and minor changes.
141 | - Updated `README.md`.
142 |
143 | #### Version 1.0.0 (11-Jan-2013)
144 | - Initial release.
145 |
146 |
147 | _|_| _|_| _| _| _|
148 | _| _| _| _| _| _|
149 | _| _| _| _| _| _|_|
150 | _|_|_|_| _|_|_| _| _|_| _|_|
151 | _| _| _| _| _| _|
152 | _| _| _| _| _| _|
153 |
154 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | New BSD License
2 | ===============
3 |
4 | Copyright (c) 2010-2016, Franck Cassedanne, OUARZ Technology Ltd.
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice,
11 | this list of conditions and the following disclaimer.
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 | * Neither the names of the copyright holders nor the names of its
16 | contributors may be used to endorse or promote products derived from this
17 | software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Apix Cache, cache-tagging for PHP
2 | [](https://travis-ci.org/apix/cache)
3 | =================================
4 | [](https://packagist.org/packages/apix/cache)
5 | [](https://packagist.org/packages/apix/cache)
6 | [](https://scrutinizer-ci.com/g/frqnck/apix-cache/build-status/master)
7 | [](https://scrutinizer-ci.com/g/frqnck/apix-cache/?branch=master)
8 | [](https://scrutinizer-ci.com/g/frqnck/apix-cache/?branch=master)
9 | [](https://packagist.org/packages/apix/cache)
10 |
11 | Apix Cache is a generic and thin cache wrapper with a PSR-6 interface to various caching backends and emphasising cache **tagging** and **indexing**.
12 |
13 | > Cache-tagging allows to find/update all data items with one or more given tags. Providing, for instance, a batch delete of all obsolete entries matching a speficic tag such as a version string.
14 |
15 | * Fully **unit-tested** and compliant with PSR-1, PSR-2, PSR-4 and **PSR-6** (Cache).
16 | * [Continuously integrated](https://travis-ci.org/apix/cache)
17 | * against **PHP** ~~5.3~~, **5.4**, **5.5**, **5.6**, **7.0**, **7.1**, **7.2**, **7.3** ~~and HHVM~~,
18 | * and against `APC`, `APCu`, `Redis`, `MongoDB`, `Sqlite`, `MySQL`, `PgSQL` and `Memcached`, ...
19 | * supports a range of serializers: `igBinary`, `msgpack`, `json`, `php`, ...
20 | * Extendable, additional extensions are available:
21 | * **[apix/simple-cache](//github.com/apix/simple-cache)** provides a SimpleCache (PSR-16) interface.
22 | * More contributions will be linked here.
23 | * Available as a [Composer](https://packagist.org/packages/apix/cache) ~~and as a [PEAR](http://pear.ouarz.net)~~ package.
24 |
25 | ⇄ *[Pull requests](//github.com/frqnck/apix-cache/blob/master/.github/CONTRIBUTING.md)* and ★ *Stars* are always welcome. For bugs and feature request, please [create an issue](//github.com/frqnck/apix-cache/issues/new).
26 |
27 | ---
28 |
29 | Cache backends
30 | --------------
31 | Currently, the following cache store are supplied:
32 |
33 | * **[`APCu`](http://php.net/apcu)** and **[APC](http://php.net/apc)** *with tagging support*,
34 | * **[Redis](#redis-specific)** using the [`PhpRedis`](https://github.com/phpredis/phpredis) extension *with tagging support*,
35 | * **[MongoDB](#mongodb-specific)** using either the new [`mongodb`](http://php.net/mongodb) or the legacy [`mongo`](http://php.net/mongo) extension *with tagging support*,
36 | * **[Memcached](#memcached-specific)** using the [`Memcached`](http://php.net/book.memcached.php) extension *with indexing, tagging and namespacing support*,
37 | * and relational databases using **[PDO](#pdo-specific)** *with tagging support*:
38 | * Dedicated drivers for **[SQLite](http://www.sqlite.org)**, **[PostgreSQL](http://www.postgresql.org)** and **[MySQL](http://www.mysql.com)** (also works with Amazon Aurora, MariaDB and Percona),
39 | * A generic **[Sql1999](https://en.wikipedia.org/wiki/SQL:1999)** driver for [4D](http://www.4d.com/), [Cubrid](http://www.cubrid.org), [SQL Server](http://www.microsoft.com/sqlserver), [Sybase](http://www.sybase.com), [Firebird](http://www.firebirdsql.org), [ODBC](https://en.wikipedia.org/wiki/Open_Database_Connectivity), [Interbase](http://www.embarcadero.com/products/interbase), [IBM DB2](www.ibm.com/software/data/db2/), [IDS](http://www-01.ibm.com/software/data/informix/), [Oracle](http://www.oracle.com/database)...
40 | * **[Directory](#filesystem-specific)** and **[Files](#filesystem-specific)** based *with tagging support*,
41 | * **Runtime**, in-memory array storage.
42 |
43 | Factory usage (PSR-Cache wrapper)
44 | -------------
45 |
46 | ```php
47 | use Apix\Cache;
48 |
49 | $backend = new \Redis();
50 | #$backend = new \PDO('sqlite:...'); // Any supported client object e.g. Memcached, MongoClient, ...
51 | #$backend = new Cache\Files($options); // or one that implements Apix\Cache\Adapter
52 | #$backend = 'apcu'; // or an adapter name (string) e.g. "APC", "Runtime"
53 | #$backend = new MyArrayObject(); // or even a plain array() or \ArrayObject.
54 |
55 | $cache = Cache\Factory::getPool($backend); // without tagging support
56 | #$cache = Cache\Factory::getTaggablePool($backend); // with tagging
57 |
58 | $item = $cache->getItem('wibble_id');
59 |
60 | if ( !$cache->isHit() ) {
61 | $data = compute_expensive_stuff();
62 | $item->set($data);
63 | $cache->save($item);
64 | }
65 |
66 | return $item->get();
67 | ```
68 |
69 | Basic usage (Apix native)
70 | -----------
71 |
72 | ```php
73 | use Apix\Cache;
74 |
75 | $cache = new Cache\Apcu;
76 |
77 | // try to retrieve 'wibble_id' from the cache
78 | if ( !$data = $cache->load('wibble_id') ) {
79 |
80 | // otherwise, get some data from the origin
81 | // example of arbitrary mixed data
82 | $data = array('foo' => 'bar');
83 |
84 | // and save it to the cache
85 | $cache->save($data, 'wibble_id');
86 | }
87 | ```
88 | You can also use the folowing in your use cases:
89 | ```php
90 | // save $data to the cache as 'wobble_id',
91 | // tagging it along the way as 'baz' and 'flob',
92 | // and set the ttl to 300 seconds (5 minutes)
93 | $cache->save($data, 'wobble_id', array('baz', 'flob'), 300);
94 |
95 | // retrieve all the cache ids under the tag 'baz'
96 | $ids = $cache->loadTag('baz');
97 |
98 | // clear out all items with a 'baz' tag
99 | $cache->clean('baz');
100 |
101 | // remove the named item
102 | $cache->delete('wibble_id');
103 |
104 | // flush out the cache (of all -your- stored items)
105 | $cache->flush();
106 | ```
107 |
108 | Advanced usage
109 | --------------
110 | ### Options shared by all the backends
111 | ```php
112 | use Apix\Cache;
113 |
114 | // default options
115 | $options = array(
116 | 'prefix_key' => 'apix-cache-key:', // prefix cache keys
117 | 'prefix_tag' => 'apix-cache-tag:', // prefix cache tags
118 | 'tag_enable' => true // wether to enable tags support
119 | );
120 |
121 | // start APCu as a local cache
122 | $local_cache = new Cache\Apcu($options);
123 | ```
124 |
125 | ### Redis specific
126 | ```php
127 | // additional (default) options
128 | $options['atomicity'] = false; // false is faster, true is guaranteed
129 | $options['serializer'] = 'php'; // null, php, igbinary, json and msgpack
130 |
131 | $redis_client = new \Redis; // instantiate phpredis*
132 | $distributed_cache = new Cache\Redis($redis_client, $options);
133 | ```
134 | \* see [PhpRedis](https://github.com/nicolasff/phpredis) for instantiation usage.
135 |
136 | ### Memcached specific
137 | ```php
138 | // additional (default) options, specific to Memcached
139 | $options['prefix_key'] = 'key_'; // prefix cache keys
140 | $options['prefix_tag'] = 'tag_'; // prefix cache tags
141 | $options['prefix_idx'] = 'idx_'; // prefix cache indexes
142 | $options['prefix_nsp'] = 'nsp_'; // prefix cache namespaces
143 | $options['serializer'] = 'auto'; // auto, igbinary, msgpack, php, json and json_array.
144 |
145 | $memcached = new \Memcached; // a Memcached*** instance
146 | $shared_cache = new Cache\Memcached($memcached, $options);
147 | ```
148 |
149 | The serialzer `auto` (default) is `igBinary` if available, then `msgpack` if available, then `php` otherwise.
150 |
151 | \*\*\* see [Memcached](http://php.net/manual/en/book.memcached.php) for instantiation details.
152 |
153 | ### MongoDB specific
154 | ```php
155 | // additional (default) options
156 | $options['object_serializer'] = 'php'; // null, json, php, igBinary
157 | $options['db_name'] = 'apix'; // name of the mongo db
158 | $options['collection_name'] = 'cache'; // name of the mongo collection
159 |
160 | $mongo = new \MongoDB\Client; // MongoDB native driver
161 | #$mongo = new \MongoClient; // or MongoDB legacy driver
162 | $cache = new Cache\Mongo($mongo, $options);
163 | ```
164 |
165 | ### PDO specific
166 | ```php
167 | // additional (default) options, specific to the PDO backends
168 | $options['db_table'] = 'cache'; // table to hold the cache
169 | $options['serializer'] = 'php'; // null, php, igbinary, json and msgpack
170 | $options['preflight'] = true; // wether to preflight the DB
171 | $options['timestamp'] = 'Y-m-d H:i:s'; // the timestamp DB format
172 |
173 | // with SQLITE
174 | $dbh = new \PDO('sqlite:/tmp/apix_tests.sqlite3');
175 | $relational_cache = new Cache\Pdo\Sqlite($dbh, $options);
176 |
177 | // with MYSQL, MariaDB and Percona
178 | $dbh = new \PDO('mysql:host=xxx;port=xxx;dbname=xxx', 'user', 'pass');
179 | $mysql_cache = new Cache\Pdo\Mysql($dbh, $options);
180 |
181 | // with PGSQL
182 | $dbh = new \PDO('pgsql:dbname=xxx;host=xxx', 'xxx', 'xxx');
183 | $postgres_cache = new Cache\Pdo\Pgsql($dbh, $options);
184 |
185 | // with a SQL:1999 compliant DB, e.g. Oracle
186 | $dbh = new \PDO('oci:dbname=xxx', 'xxx', 'xxx');
187 | $sql1999_cache = new Cache\Pdo\Sql1999($dbh, $options);
188 | ```
189 | The `preflight` option will create on-the-fly the required tables if these are mssing.
190 | Once these tables exist, set `preflight` to `false` in order to avoid the extraneous checks.
191 |
192 | ### Filesystem specific
193 |
194 | ```php
195 | // additional (default) options
196 | $options['directory'] = sys_get_temp_dir() . '/apix-cache'; // Directory where the cache is created
197 | $options['locking'] = true; // File locking (recommended)
198 |
199 | $files_cache = new Cache\Files($options);
200 | // or
201 | $directory_cache = new Cache\Directory($options);
202 | ```
203 |
204 | - **Files**: the cache metadata (expiration time and tags) are stored in the cache file directly.
205 | - **Directory**: the metadata are stored separately from the cached data.
206 |
207 | Installation
208 | ------------------------
209 |
210 | This project adheres to [Semantic Versioning](http://semver.org/) and can be installed using composer:
211 |
212 | $ composer require apix/cache:^1.3
213 |
214 | All notable changes to this project are documented in its [CHANGELOG](CHANGELOG.md).
215 |
216 | License
217 | -------
218 | This work is licensed under the New BSD license -- see the [LICENSE](LICENSE.txt) for the full details.
Copyright (c) 2010-2016 Franck Cassedanne
219 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apix/cache",
3 | "description": "A thin PSR-6 cache wrapper with a generic interface to various caching backends emphasising cache taggging and indexing to Redis, Memcached, PDO/SQL, APC and other adapters.",
4 | "type": "library",
5 | "keywords": ["apc", "APCu", "redis", "mongodb", "mongo", "igbinary", "serializer", "json", "pdo", "sqlite", "mysql", "postgres", "pgsql", "memcached", "memcache", "psrCache", "psr-6", "psr-cache", "Filesystem", "session", "cache", "caching", "msgpack", "tagging"],
6 | "homepage": "https://github.com/frqnck/apix-cache",
7 | "license": "BSD-3-Clause",
8 | "authors": [
9 | {
10 | "name": "Franck Cassedanne",
11 | "email": "franck@ouarz.net"
12 | },
13 | {
14 | "name": "Apix Cache Community",
15 | "homepage": "https://github.com/frqnck/apix-cache/contributors"
16 | }
17 | ],
18 | "support": {
19 | "irc": "irc://irc.freenode.org/ouarz"
20 | },
21 | "require": {
22 | "php": ">=5.3.0",
23 | "psr/cache": "^1.0"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "^4.0|^5.0",
27 | "php-coveralls/php-coveralls": "~1.1"
28 | },
29 | "suggest": {
30 | "ext-apc": "Allows to cache into APC data store ~ up to PHP 5.4.",
31 | "ext-apcu": "Allows to use APCu userland caching ~ any PHP version.",
32 | "ext-redis": "So you can cache into Redis servers.",
33 | "ext-mongo": "Allows to cache into MongoDB instances using http://php.net/mongo ~ up to PHP 5.6.",
34 | "ext-mongodb": "Allows to cache into MongoDB instances using https://php.net/mongodb ~ from PHP 7 and above and HHVM.",
35 | "ext-memcached": "Allows to use Memcached distributed memory caching system",
36 | "ext-pdo": "Allows to cache into PDO supported DBs such as Oracle, MS SQL server, IBM DB2.",
37 | "ext-pdo_sqlite": "Allows to cache into SQLite.",
38 | "ext-pdo_mysql": "Allows to use MySQL.",
39 | "ext-pdo_pgsql": "Allows to use PostgreSQL",
40 | "ext-igbinary": "Fast and small serialization format",
41 | "msgpack/msgpack-php": "MessagePack serialization format"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "Apix\\Cache\\": "src/"
46 | }
47 | },
48 | "autoload-dev": {
49 | "psr-4": {
50 | "Apix\\Cache\\tests\\": "tests/"
51 | }
52 | },
53 | "provide": {
54 | "psr/cache-implementation": "^1.0"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/AbstractCache.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * Base class provides the cache wrappers structure.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | abstract class AbstractCache implements Adapter
21 | {
22 |
23 | /**
24 | * Holds an injected adapter.
25 | * @var object
26 | */
27 | protected $adapter = null;
28 |
29 | /**
30 | * @var Serializer\Adapter
31 | */
32 | protected $serializer;
33 |
34 | /**
35 | * Holds some generic default options.
36 | * @var array
37 | */
38 | protected $options = array(
39 | 'prefix_key' => 'apix-cache-key:', // prefix cache keys
40 | 'prefix_tag' => 'apix-cache-tag:', // prefix cache tags
41 | 'tag_enable' => true // wether to enable tagging
42 | );
43 |
44 | /**
45 | * Constructor use to set the adapter and dedicated options.
46 | *
47 | * @param object|null $adapter The adapter to set, generally an object.
48 | * @param array|null $options The array of user options.
49 | */
50 | public function __construct($adapter=null, array $options=null)
51 | {
52 | $this->adapter = $adapter;
53 | $this->setOptions($options);
54 | }
55 |
56 | /**
57 | * Sets and merges the options (overriding the default options).
58 | *
59 | * @param array|null $options The array of user options.
60 | */
61 | public function setOptions(array $options=null)
62 | {
63 | if (null !== $options) {
64 | $this->options = $options+$this->options;
65 | }
66 | }
67 |
68 | /**
69 | * Returns the named option.
70 | *
71 | * @param string $key
72 | * @return mixed
73 | */
74 | public function getOption($key)
75 | {
76 | if (!isset($this->options[$key])) {
77 | throw new PsrCache\InvalidArgumentException(
78 | sprintf('Invalid option "%s"', $key)
79 | );
80 | }
81 |
82 | return $this->options[$key];
83 | }
84 |
85 | /**
86 | * Sets the named option.
87 | *
88 | * @param string $key
89 | * @param mixed $value
90 | */
91 | public function setOption($key, $value)
92 | {
93 | return $this->options[$key] = $value;
94 | }
95 |
96 | /**
97 | * Returns a prefixed and sanitased cache id.
98 | *
99 | * @param string $key The base key to prefix.
100 | * @return string
101 | */
102 | public function mapKey($key)
103 | {
104 | return $this->sanitise($this->options['prefix_key'] . $key);
105 | }
106 |
107 | /**
108 | * Returns a prefixed and sanitased cache tag.
109 | *
110 | * @param string $tag The base tag to prefix.
111 | * @return string
112 | */
113 | public function mapTag($tag)
114 | {
115 | return $this->sanitise($this->options['prefix_tag'] . $tag);
116 | }
117 |
118 | /**
119 | * Returns a sanitased string for keying/tagging purpose.
120 | *
121 | * @param string $key The string to sanitise.
122 | * @return string
123 | */
124 | public function sanitise($key)
125 | {
126 | return $key;
127 | // return str_replace(array('/', '\\', ' '), '_', $key);
128 | }
129 |
130 | /**
131 | * Gets the injected adapter.
132 | *
133 | * @return object
134 | */
135 | public function getAdapter()
136 | {
137 | return $this->adapter;
138 | }
139 |
140 | /**
141 | * Sets the serializer.
142 | *
143 | * @param string $name
144 | */
145 | public function setSerializer($name)
146 | {
147 | if (null === $name) {
148 | $this->serializer = null;
149 | } else {
150 | $classname = __NAMESPACE__ . '\Serializer\\';
151 | $classname .= ucfirst(strtolower($name));
152 | $this->serializer = new $classname();
153 | }
154 | }
155 |
156 | /**
157 | * Gets the serializer.
158 | *
159 | * @return Serializer\Adapter
160 | */
161 | public function getSerializer()
162 | {
163 | return $this->serializer;
164 | }
165 |
166 | /**
167 | * Retrieves the cache content for the given key or the keys for a given tag.
168 | *
169 | * @param string $key The cache id to retrieve.
170 | * @param string $type The type of the key (either 'key' or 'tag').
171 | * @return mixed|null Returns the cached data or null if not set.
172 | */
173 | public function load($key, $type='key')
174 | {
175 | return $type == 'key' ? $this->loadKey($key) : $this->loadTag($key);
176 | }
177 |
178 | /**
179 | * Returns the given string without the given prefix.
180 | *
181 | * @param string $str The subject string
182 | * @param string $prefix The prefix to remove
183 | * @return string
184 | */
185 | public function removePrefix($str, $prefix)
186 | {
187 | return substr($str, 0, strlen($prefix)) == $prefix
188 | ? substr($str, strlen($prefix))
189 | : $str;
190 | }
191 |
192 | /**
193 | * Returns the given string without the internal key prefix.
194 | *
195 | * @param string $str
196 | * @return string
197 | */
198 | public function removePrefixKey($str)
199 | {
200 | return $this->removePrefix($str, $this->options['prefix_key']);
201 | }
202 |
203 | /**
204 | * Returns the given string without the internal tag prefix.
205 | *
206 | * @param string $str
207 | * @return string
208 | */
209 | public function removePrefixTag($str)
210 | {
211 | return $this->removePrefix($str, $this->options['prefix_tag']);
212 | }
213 |
214 | }
215 |
--------------------------------------------------------------------------------
/src/AbstractPdo.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * PDO cache wrapper with tag support.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | abstract class AbstractPdo extends AbstractCache
21 | {
22 |
23 | /**
24 | * Holds the SQL definitions.
25 | * @var array
26 | */
27 | protected $sql_definitions;
28 |
29 | /**
30 | * Holds the array of TTLs.
31 | * @var array
32 | */
33 | protected $ttls = array();
34 |
35 | /**
36 | * Constructor.
37 | *
38 | * @param \PDO $pdo An instance of a PDO class.
39 | * @param array $options Array of options.
40 | */
41 | public function __construct(\PDO $pdo, array $options=null)
42 | {
43 | // default options
44 | $this->options['db_table'] = 'cache'; // table to hold the cache
45 | $this->options['serializer'] = 'php'; // null, php, igBinary, json
46 | // and msgpack
47 | $this->options['preflight'] = true; // wether to preflight the DB
48 | $this->options['timestamp'] = 'Y-m-d H:i:s'; // timestamp db format
49 |
50 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
51 |
52 | parent::__construct($pdo, $options);
53 | $this->setSerializer($this->options['serializer']);
54 |
55 | if ($this->options['preflight']) {
56 | $this->initDb();
57 | }
58 | }
59 |
60 | /**
61 | * Initialises the database and its indexes (if required, non-destructive).
62 | *
63 | * @return self Provides a fluent interface
64 | */
65 | public function initDb()
66 | {
67 | $this->adapter->exec( $this->getSql('init') );
68 |
69 | $this->createIndexTable('key_idx');
70 | $this->createIndexTable('exp_idx');
71 | $this->createIndexTable('tag_idx');
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Creates the specified indexe table (if missing).
78 | *
79 | * @param string $index
80 | * @return boolean
81 | */
82 | public function createIndexTable($index)
83 | {
84 | if (!isset($this->sql_definitions[$index])) {
85 | return false;
86 | }
87 |
88 | try {
89 | $this->adapter->exec($this->getSql($index, $this->options['db_table']));
90 | } catch (\PDOException $ex) {
91 | if (1061 !== $ex->errorInfo[1]) {
92 | // if not "Index already exists"
93 | }
94 | }
95 |
96 | return $this->adapter->errorCode() == '00000';
97 | }
98 |
99 | /**
100 | * {@inheritdoc}
101 | */
102 | public function loadKey($key)
103 | {
104 | $result = null;
105 |
106 | $key = $this->mapKey($key);
107 | $sql = $this->getSql('loadKey');
108 | $values = array('key' => $key, 'now' => time());
109 |
110 | $cached = $this->exec($sql, $values)->fetch();
111 |
112 | if (false !== $cached) {
113 | $this->ttls[$key] = $cached['expire'];
114 |
115 | if (null !== $cached['data'] && null !== $this->serializer) {
116 | $result = $this->serializer->unserialize($cached['data']);
117 | }
118 | else {
119 | $result = $cached['data'];
120 | }
121 | }
122 |
123 | return $result;
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function loadTag($tag)
130 | {
131 | $sql = $this->getSql('loadTag');
132 | // $tag = $this->mapTag($tag);
133 | $values = array('tag' => "%$tag%", 'now' => time());
134 |
135 | $items = $this->exec($sql, $values)->fetchAll();
136 |
137 | $keys = array();
138 | foreach ($items as $item) {
139 | $keys[] = $item['key'];
140 | }
141 |
142 | return empty($keys) ? null : $keys;
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function save($data, $key, array $tags=null, $ttl=null)
149 | {
150 | $key = $this->mapKey($key);
151 | $values = array(
152 | 'key' => $key,
153 | 'data' => null !== $this->serializer
154 | ? $this->serializer->serialize($data)
155 | : $data,
156 | 'exp' => null !== $ttl && 0 !== $ttl ? time()+$ttl : null,
157 | 'dated' => $this->getTimestamp()
158 | );
159 |
160 | $this->ttls[$key] = $values['exp'];
161 |
162 | $values['tags'] = $this->options['tag_enable'] && null !== $tags
163 | ? implode(', ', $tags)
164 | : null;
165 |
166 | // upsert
167 | $sql = $this->getSql('update');
168 | $nb = $this->exec($sql, $values)->rowCount();
169 | if ($nb == 0) {
170 | $sql = $this->getSql('insert');
171 | $nb = $this->exec($sql, $values)->rowCount();
172 | }
173 |
174 | return (boolean) $nb;
175 | }
176 |
177 | /**
178 | * {@inheritdoc}
179 | */
180 | public function delete($key)
181 | {
182 | $sql = $this->getSql('delete');
183 | $values = array($this->mapKey($key));
184 |
185 | return (boolean) $this->exec($sql, $values)->rowCount();
186 | }
187 |
188 | /**
189 | * {@inheritdoc}
190 | */
191 | public function clean(array $tags)
192 | {
193 | $values = array();
194 | foreach ($tags as $tag) {
195 | // $tag = $this->mapTag($tag);
196 | $values[] = '%' . $tag . '%';
197 | }
198 |
199 | $sql = $this->getSql(
200 | 'clean', implode(' OR ', array_fill(
201 | 0, count($tags), $this->getSql('clean_like')))
202 | );
203 |
204 | return (boolean) $this->exec($sql, $values)->rowCount();
205 | }
206 |
207 | /**
208 | * {@inheritdoc}
209 | */
210 | public function flush($all=false)
211 | {
212 | if (true === $all) {
213 | return false !== $this->adapter->exec($this->getSql('flush_all'));
214 | }
215 |
216 | $value = array(
217 | $this->options['prefix_key'].'%',
218 | $this->options['prefix_tag'].'%'
219 | );
220 |
221 | return (boolean) $this->exec($this->getSql('flush'), $value)->rowCount();
222 | }
223 |
224 | /**
225 | * Purges expired items.
226 | *
227 | * @param integer|null $add Extra time in second to add.
228 | * @return boolean Returns True on success or False on failure.
229 | */
230 | public function purge($add=null)
231 | {
232 | $time = null == $add ? time() : time()+$add;
233 |
234 | return (boolean) $this->adapter->exec($this->getSql('purge', $time));
235 | }
236 |
237 | /**
238 | * Gets the named SQL definition.
239 | *
240 | * @param string $key
241 | * @param string|integer $value An additional value.
242 | * @return string
243 | */
244 | protected function getSql($key, $value=null)
245 | {
246 | return sprintf(
247 | $this->sql_definitions[$key],
248 | $this->options['db_table'],
249 | $value
250 | );
251 | }
252 |
253 | /**
254 | * Prepares and executes a SQL query.
255 | *
256 | * @param string $sql The SQL to prepare.
257 | * @param array $values The values to execute.
258 | * @return \PDOStatement Provides a fluent interface
259 | */
260 | protected function exec($sql, array $values)
261 | {
262 | $prep = $this->adapter->prepare($sql);
263 | $prep->execute($values);
264 |
265 | return $prep;
266 | }
267 |
268 | /**
269 | * Returns the current driver's name.
270 | *
271 | * @param \PDO $pdo An instance of a PDO class.
272 | * @return string Either 'Mysql', 'Pgsql', 'Sqlite' or 'Sql1999'.
273 | */
274 | public static function getDriverName(\PDO $pdo)
275 | {
276 | $name = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
277 | if (!in_array($name, array('sqlite', 'mysql', 'pgsql'))) {
278 | // @codeCoverageIgnoreStart
279 | // sql1999 specific tests are run on Sqlite.
280 | $name = 'sql1999';
281 | }
282 | // @codeCoverageIgnoreEnd
283 |
284 | return ucfirst($name);
285 | }
286 |
287 | /**
288 | * Returns a formated timestamp.
289 | *
290 | * @param integer|null $time If null, use the current time.
291 | */
292 | public function getTimestamp($time=null)
293 | {
294 | return date(
295 | $this->options['timestamp'],
296 | null != $time ? $time : time()
297 | );
298 | }
299 |
300 | /**
301 | * {@inheritdoc}
302 | */
303 | public function getTtl($key)
304 | {
305 | $mKey = $this->mapKey($key);
306 |
307 | return isset($this->ttls[$mKey])
308 | ? $this->ttls[$mKey]-time()
309 | : false;
310 | }
311 |
312 | }
313 |
--------------------------------------------------------------------------------
/src/Adapter.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * The interface/adapter that the cache wrappers must implement.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | interface Adapter
21 | {
22 |
23 | /**
24 | * Retrieves the cache content for the given key.
25 | *
26 | * @param string $key The cache key to retrieve.
27 | * @return mixed|null Returns the cached data or null.
28 | */
29 | public function loadKey($key);
30 |
31 | /**
32 | * Retrieves the cache keys for the given tag.
33 | *
34 | * @param string $tag The cache tag to retrieve.
35 | * @return array|null Returns an array of cache keys or null.
36 | */
37 | public function loadTag($tag);
38 |
39 | /**
40 | * Saves data to the cache.
41 | *
42 | * @param mixed $data The data to cache.
43 | * @param string $key The cache id to save.
44 | * @param array $tags The cache tags for this cache entry.
45 | * @param int $ttl The time-to-live in seconds, if set to null the
46 | * cache is valid forever.
47 | * @return boolean Returns True on success or False on failure.
48 | */
49 | public function save($data, $key, array $tags=null, $ttl=null);
50 |
51 | /**
52 | * Deletes the specified cache record.
53 | *
54 | * @param string $key The cache id to remove.
55 | * @return boolean Returns True on success or False on failure.
56 | */
57 | public function delete($key);
58 |
59 | /**
60 | * Removes all the cached entries associated with the given tag names.
61 | *
62 | * @param array $tags The array of tags to remove.
63 | * @return boolean Returns True on success or False on failure.
64 | */
65 | public function clean(array $tags);
66 |
67 | /**
68 | * Flush all the cached entries.
69 | *
70 | * @param boolean $all Wether to flush the whole database, or (preferably)
71 | * the entries prefixed with prefix_key and prefix_tag.
72 | * @return boolean Returns True on success or False on failure.
73 | */
74 | public function flush($all=false);
75 |
76 | /**
77 | * Returns the time-to-live (in seconds) for the given key.
78 | *
79 | * @param string $key The name of the key.
80 | * @return int|false Returns the number of seconds left, 0 if valid
81 | * forever or False if the key is non-existant.
82 | */
83 | public function getTtl($key);
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/Apc.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * APC Cache and APCu (User-Cache) wrapper with emulated tag support.
17 | *
18 | * @package Apix\Cache
19 | * @author Franck Cassedanne
20 | */
21 | class Apc extends AbstractCache
22 | {
23 |
24 | /**
25 | * Constructor.
26 | */
27 | public function __construct(array $options = array())
28 | {
29 | parent::__construct(null, $options);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function loadKey($key)
36 | {
37 | return $this->get($this->mapKey($key));
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function loadTag($tag)
44 | {
45 | return $this->get($this->mapTag($tag));
46 | // TODO: return $this->getIndex($this->mapTag($tag))->load();
47 | }
48 |
49 | /**
50 | * Retrieves the cache item for the given id.
51 | *
52 | * @param string $id The cache id to retrieve.
53 | * @param boolean $success The variable to store the success value.
54 | * @return mixed|null Returns the cached data or null.
55 | */
56 | public function get($id, $success = null)
57 | {
58 | $cached = apc_fetch($id, $success);
59 |
60 | return false === $success ? null : $cached;
61 | }
62 |
63 | /**
64 | * Returns the named indexer.
65 | *
66 | * @param string $name The name of the index.
67 | * @return Indexer\Adapter
68 | */
69 | // public function getIndex($name)
70 | // {
71 | // return new Indexer\ApcIndexer($name, $this);
72 | // }
73 |
74 | /**
75 | * {@inheritdoc}
76 | *
77 | * APC does not support natively cache-tags so we simulate them.
78 | */
79 | public function save($data, $key, array $tags = null, $ttl = null)
80 | {
81 | $key = $this->mapKey($key);
82 | $store = array($key => $data);
83 |
84 | if ($this->options['tag_enable'] && !empty($tags)) {
85 |
86 | // add all the tags to the index key.
87 | // TODO: $this->getIndex($key)->add($tags);
88 |
89 | foreach ($tags as $tag) {
90 | $tag = $this->mapTag($tag);
91 | $keys = apc_fetch($tag, $success);
92 | if (false === $success) {
93 | $store[$tag] = array($key);
94 | } else {
95 | $keys[] = $key;
96 | $store[$tag] = array_unique($keys);
97 | }
98 | }
99 | }
100 |
101 | return !in_array(false, apc_store($store, null, $ttl));
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | *
107 | * APC does not support natively cache-tags so we simulate them.
108 | */
109 | public function delete($key)
110 | {
111 | $key = $this->mapKey($key);
112 |
113 | if ( ($success = apc_delete($key)) && $this->options['tag_enable']) {
114 |
115 | $iterator = $this->getIterator(
116 | '/^' . preg_quote($this->options['prefix_tag']) . '/',
117 | \APC_ITER_VALUE
118 | );
119 | foreach ($iterator as $tag => $keys) {
120 | if ( false !== ($k = array_search($key, $keys['value'])) ) {
121 | unset($keys['value'][$k]);
122 | if (empty($keys['value'])) {
123 | apc_delete($tag);
124 | } else {
125 | apc_store($tag, $keys['value']);
126 | }
127 | }
128 | continue;
129 | }
130 | }
131 |
132 | return $success;
133 | }
134 |
135 | /**
136 | * {@inheritdoc}
137 | *
138 | * APC does not support natively cache-tags so we simulate them.
139 | */
140 | public function clean(array $tags)
141 | {
142 | $rmed = array();
143 | foreach ($tags as $tag) {
144 | $tag = $this->mapTag($tag);
145 | $keys = apc_fetch($tag, $success);
146 | if ($success) {
147 | foreach ($keys as $key) {
148 | $rmed[] = apc_delete($key);
149 | }
150 | $rmed[] = apc_delete($tag);
151 | } else {
152 | $rmed[] = false;
153 | }
154 | }
155 |
156 | return !in_array(false, $rmed);
157 | }
158 |
159 | /**
160 | * {@inheritdoc}
161 | *
162 | * APC does not support natively cache-tags so we simulate them.
163 | */
164 | public function flush($all = false)
165 | {
166 | if (true === $all) {
167 | return apc_clear_cache('user');
168 | }
169 |
170 | $iterator = $this->getIterator(
171 | '/^' . preg_quote($this->options['prefix_key'])
172 | .'|' . preg_quote($this->options['prefix_tag']) . '/',
173 | \APC_ITER_KEY
174 | );
175 |
176 | $rmed = array();
177 | foreach ($iterator as $key => $data) {
178 | $rmed[] = apc_delete($key);
179 | }
180 |
181 | return empty($rmed) || in_array(false, $rmed) ? false : true;
182 | }
183 |
184 | /**
185 | * Returns an APC iterator.
186 | *
187 | * @param string $search
188 | * @param integer $format
189 | * @return \APCIterator
190 | */
191 | protected function getIterator($search = null, $format = \APC_ITER_ALL)
192 | {
193 | return new \APCIterator('user', $search, $format, 100, \APC_LIST_ACTIVE);
194 | }
195 |
196 | /**
197 | * Returns some internal informations about a APC cached item.
198 | *
199 | * @param string $key
200 | * @return array|false
201 | */
202 | public function getInternalInfos($key)
203 | {
204 | $iterator = $this->getIterator(
205 | '/^' . preg_quote($this->options['prefix_key']) . '/',
206 | \APC_ITER_KEY | \APC_ITER_VALUE | \APC_ITER_TTL
207 | );
208 |
209 | $key = $this->mapKey($key);
210 | foreach ($iterator as $k => $v) {
211 | if ($k != $key)
212 | continue;
213 |
214 | return $v;
215 | }
216 |
217 | return false;
218 | }
219 |
220 | /**
221 | * {@inheritdoc}
222 | */
223 | public function getTtl($key)
224 | {
225 | $info = $this->getInternalInfos($key);
226 | if ( $info && isset($info['ttl']) ) {
227 | return $info['ttl'];
228 | // return $info['ttl'] > 0 ? $info['creation_time']+$info['ttl'] : 0;
229 | }
230 |
231 | return false;
232 | }
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/src/Apcu.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * APCu Cache(User-Cache) wrapper with emulated tag support.
17 | *
18 | * @package Apix\Cache
19 | * @author Franck Cassedanne
20 | */
21 | class Apcu extends Apc
22 | {
23 |
24 | /**
25 | * Retrieves the cache item for the given id.
26 | *
27 | * @param string $id The cache id to retrieve.
28 | * @param boolean $success The variable to store the success value.
29 | * @return mixed|null Returns the cached data or null.
30 | */
31 | public function get($id, $success = null)
32 | {
33 | $cached = apcu_fetch($id, $success);
34 |
35 | return false === $success ? null : $cached;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | *
41 | * APC does not support natively cache-tags so we simulate them.
42 | */
43 | public function save($data, $key, array $tags = null, $ttl = null)
44 | {
45 | $key = $this->mapKey($key);
46 | $store = array($key => $data);
47 |
48 | if ($this->options['tag_enable'] && !empty($tags)) {
49 |
50 | // add all the tags to the index key.
51 | // TODO: $this->getIndex($key)->add($tags);
52 |
53 | foreach ($tags as $tag) {
54 | $tag = $this->mapTag($tag);
55 | $keys = apcu_fetch($tag, $success);
56 | if (false === $success) {
57 | $store[$tag] = array($key);
58 | } else {
59 | $keys[] = $key;
60 | $store[$tag] = array_unique($keys);
61 | }
62 | }
63 | }
64 |
65 | return !in_array(false, apcu_store($store, null, $ttl));
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | *
71 | * APC does not support natively cache-tags so we simulate them.
72 | */
73 | public function delete($key)
74 | {
75 | $key = $this->mapKey($key);
76 |
77 | if (($success = apcu_delete($key)) && $this->options['tag_enable']) {
78 | $iterator = $this->getIterator(
79 | '/^' . preg_quote($this->options['prefix_tag']) . '/',
80 | \APC_ITER_VALUE
81 | );
82 | foreach ($iterator as $tag => $keys) {
83 | if (false !== ($k = array_search($key, $keys['value']))) {
84 | unset($keys['value'][$k]);
85 | if (empty($keys['value'])) {
86 | apcu_delete($tag);
87 | } else {
88 | apcu_store($tag, $keys['value']);
89 | }
90 | }
91 | continue;
92 | }
93 | }
94 |
95 | return $success;
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | *
101 | * APC does not support natively cache-tags so we simulate them.
102 | */
103 | public function clean(array $tags)
104 | {
105 | $rmed = array();
106 | foreach ($tags as $tag) {
107 | $tag = $this->mapTag($tag);
108 | $keys = apcu_fetch($tag, $success);
109 | if ($success) {
110 | foreach ($keys as $key) {
111 | $rmed[] = apcu_delete($key);
112 | }
113 | $rmed[] = apcu_delete($tag);
114 | } else {
115 | $rmed[] = false;
116 | }
117 | }
118 |
119 | return !in_array(false, $rmed);
120 | }
121 |
122 | /**
123 | * {@inheritdoc}
124 | *
125 | * APC does not support natively cache-tags so we simulate them.
126 | */
127 | public function flush($all = false)
128 | {
129 | if (true === $all) {
130 | return apcu_clear_cache();
131 | }
132 |
133 | $iterator = $this->getIterator(
134 | '/^' . preg_quote($this->options['prefix_key'])
135 | .'|' . preg_quote($this->options['prefix_tag']) . '/',
136 | \APC_ITER_KEY
137 | );
138 |
139 | $rmed = array();
140 | foreach ($iterator as $key => $data) {
141 | $rmed[] = apcu_delete($key);
142 | }
143 |
144 | return empty($rmed) || in_array(false, $rmed) ? false : true;
145 | }
146 |
147 | /**
148 | * Returns an APC iterator.
149 | *
150 | * @param string $search
151 | * @param integer $format
152 | * @return \APCIterator
153 | */
154 | protected function getIterator($search = null, $format = \APC_ITER_ALL)
155 | {
156 | return class_exists('\APCUIterator')
157 | ? new \APCUIterator($search, $format, 100, \APC_LIST_ACTIVE)
158 | : new \APCIterator('user', $search, $format, 100, \APC_LIST_ACTIVE);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Exception.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
9 | *
10 | */
11 |
12 | namespace Apix\Cache;
13 |
14 | /**
15 | * Main Cache Exception.
16 | */
17 | class Exception extends \Exception implements \Psr\Cache\CacheException { }
18 |
--------------------------------------------------------------------------------
/src/Factory.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
9 | *
10 | */
11 |
12 | namespace Apix\Cache;
13 |
14 | use Apix\Cache;
15 |
16 | /**
17 | * Apix Cache Factory class provides a PSR-Cache wrapper.
18 | *
19 | * @author Franck Cassedanne
20 | */
21 | class Factory
22 | {
23 |
24 | const ERROR = '%s requires either a supported client object e.g. "\Redis", "\MongoClient", ...; or an object that implements Apix\Cache\Adapter; or an adapter name (string) e.g. "APC", "array"; or even an a plain array().';
25 |
26 | /**
27 | * Holds an array of supported cache clients.
28 | * @var array
29 | */
30 | public static $clients = array(
31 | 'Runtime', 'Array', 'ArrayObject', 'Apc', 'Apcu',
32 | 'Redis', 'MongoClient', 'Memcached', 'PDO',
33 | 'Files', 'Directory'
34 | );
35 |
36 | /**
37 | * Holds an associative array of cache adapters.
38 | * @var array
39 | */
40 | public static $adapters = array(
41 | 'MongoClient' => 'Mongo',
42 | 'PDO' => 'Pdo',
43 | 'ArrayObject' => 'Runtime'
44 | );
45 |
46 | /**
47 | * Factory pattern.
48 | *
49 | * @param mixed $mix Either a supported client object e.g. '\Redis';
50 | * or one that implements \Apix\Cache\Adapter;
51 | * or an adapter name (string) e.g. "APC", "Runtime";
52 | * or even a plain array() or \ArrayObject.
53 | * @param array $options An array of options
54 | * @param boolean $taggable Wether to return a taggable pool.
55 | * @return PsrCache\Pool|PsrCache\TaggablePool
56 | * @throws PsrCache\InvalidArgumentException
57 | * @throws Apix\Cache\Exception
58 | */
59 | public static function getPool($mix, array $options=array(), $taggable=false)
60 | {
61 | switch (true) {
62 |
63 | case is_a($mix, 'Apix\Cache\Adapter'):
64 | $class = $mix;
65 | $mix = null;
66 | break;
67 |
68 | case is_object($mix)
69 | && in_array($name = get_class($mix), self::$clients):
70 |
71 | if ($name == 'PDO') {
72 | $name = 'Pdo\\' . AbstractPdo::getDriverName($mix);
73 | } else {
74 | $name = isset(self::$adapters[$name])
75 | ? self::$adapters[$name]
76 | : $name;
77 | }
78 | break;
79 |
80 | case is_string($mix)
81 | && in_array(
82 | $name = strtolower($mix),
83 | $clients = array_map('strtolower', self::$clients)
84 | ):
85 | $key = array_search($name, $clients);
86 | $name = self::$clients[$key];
87 | $name = $name == 'Array' || $name == 'ArrayObject' ? 'Runtime' : $name;
88 | $mix = null;
89 | break;
90 |
91 | case is_array($mix):
92 | $name = 'Runtime';
93 | break;
94 |
95 | default:
96 | throw new PsrCache\InvalidArgumentException(
97 | sprintf(self::ERROR, __CLASS__)
98 | );
99 | }
100 |
101 | if (!isset($class)) {
102 | $class = '\Apix\Cache\\' . $name;
103 | }
104 |
105 | $cache = null === $mix
106 | ? new $class($options)
107 | : new $class($mix, $options);
108 |
109 | return $taggable
110 | ? new PsrCache\TaggablePool($cache)
111 | : new PsrCache\Pool($cache);
112 | }
113 |
114 | /**
115 | * @see self::getPool
116 | * @return PsrCache\TaggablePool
117 | */
118 | public static function getTaggablePool($mix, array $options=array())
119 | {
120 | return self::getPool($mix, $options, true);
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/src/Files.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
9 | *
10 | */
11 |
12 | namespace Apix\Cache;
13 |
14 | /**
15 | * Class Files
16 | * In files cache wrapper.
17 | * Expiration time and tags are stored in the cache file
18 | *
19 | * @package Apix\Cache
20 | * @author MacFJA
21 | */
22 | class Files extends AbstractCache
23 | {
24 | /**
25 | * Constructor.
26 | *
27 | * @param array $options Array of options.
28 | */
29 | public function __construct(array $options = array())
30 | {
31 | $options += array(
32 | 'directory' => sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'apix-cache',
33 | 'locking' => true
34 | );
35 | parent::__construct(null, $options);
36 | if (!file_exists($this->getOption('directory')) || !is_dir($this->getOption('directory'))) {
37 | mkdir($this->getOption('directory'), 0755, true);
38 | }
39 | }
40 |
41 | /**
42 | * Retrieves the cache content for the given key.
43 | *
44 | * @param string $key The cache key to retrieve.
45 | * @return mixed|null Returns the cached data or null.
46 | */
47 | public function loadKey($key)
48 | {
49 | $key = $this->mapKey($key);
50 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
51 |
52 | if (!file_exists($path) || !is_file($path)) {
53 | return null;
54 | }
55 |
56 | $data = $this->readFile($path);
57 |
58 | if ('' === $data) {
59 | unlink($path);
60 | return null;
61 | }
62 | $pos0 = strpos($data, PHP_EOL, 0);
63 | if (false === $pos0) {// Un-complete file
64 | unlink($path);
65 | return null;
66 | }
67 |
68 | $eolLen = strlen(PHP_EOL);
69 | $pos = strpos($data, PHP_EOL, $pos0+$eolLen);
70 | if (false === $pos) {// Un-complete file
71 | unlink($path);
72 | return null;
73 | }
74 |
75 | $expire = (int)substr($data, $pos0 + $eolLen, $pos - $pos0 - $eolLen);
76 | if ($expire != 0 && ($expire - time() < 0)) { // expired
77 | unlink($path);
78 | return null;
79 | }
80 |
81 | $serialized = substr($data, $pos+$eolLen);
82 | return unserialize($serialized);
83 | }
84 |
85 | /**
86 | * Retrieves the cache keys for the given tag.
87 | *
88 | * @param string $tag The cache tag to retrieve.
89 | * @return array|null Returns an array of cache keys or null.
90 | */
91 | public function loadTag($tag)
92 | {
93 | if (!$this->getOption('tag_enable')) {
94 | return null;
95 | }
96 |
97 | $encoded = base64_encode($this->mapTag($tag));
98 | $found = array();
99 | $files = scandir($this->getOption('directory'));
100 | foreach ($files as $file) {
101 | if (substr($file, 0, 1) === '.') {
102 | continue;
103 | }
104 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
105 | $fileTags = explode(' ', $this->readFile($path, 1));
106 |
107 | if (in_array($encoded, $fileTags, true)) {
108 | $found[] = base64_decode($file);
109 | }
110 | }
111 |
112 | if (0 === count($found)) {
113 | return null;
114 | }
115 | return $found;
116 | }
117 |
118 | /**
119 | * Get the file data.
120 | * If enable, lock file to preserve atomicity
121 | *
122 | * @param string $path The file path
123 | * @param int $line The line to read. If -1 read the whole file
124 | * @return string
125 | */
126 | protected function readFile($path, $line = -1)
127 | {
128 | $handle = fopen($path, 'r');
129 | if ($this->getOption('locking')) {
130 | flock($handle, LOCK_SH);
131 | }
132 |
133 | if (-1 === $line) {
134 | $data = stream_get_contents($handle);
135 | } else {
136 | for ($read = 1; $read < $line; $read++) {
137 | fgets($handle);
138 | }
139 | $data = rtrim(fgets($handle), PHP_EOL);
140 | }
141 |
142 | if ($this->getOption('locking')) {
143 | flock($handle, LOCK_UN);
144 | }
145 | fclose($handle);
146 |
147 | return $data;
148 | }
149 |
150 | /**
151 | * Saves data to the cache.
152 | *
153 | * @param mixed $data The data to cache.
154 | * @param string $key The cache id to save.
155 | * @param array $tags The cache tags for this cache entry.
156 | * @param int $ttl The time-to-live in seconds, if set to null the
157 | * cache is valid forever.
158 | * @return boolean Returns True on success or False on failure.
159 | */
160 | public function save($data, $key, array $tags = null, $ttl = null)
161 | {
162 | $key = $this->mapKey($key);
163 | $expire = (null === $ttl) ? 0 : time() + $ttl;
164 |
165 | $tag = '';
166 | if (null !== $tags) {
167 | $baseTags = $tags;
168 | array_walk($baseTags, function (&$item, $key, Files $cache) {
169 | $item = base64_encode($cache->mapTag($item));
170 | }, $this);
171 | $tag = implode(' ', $baseTags);
172 | }
173 |
174 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
175 | file_put_contents(
176 | $path,
177 | $tag . PHP_EOL . $expire . PHP_EOL . serialize($data),
178 | $this->getOption('locking') ? LOCK_EX : null
179 | );
180 | return true;
181 | }
182 |
183 | /**
184 | * Deletes the specified cache record.
185 | *
186 | * @param string $key The cache id to remove.
187 | * @return boolean Returns True on success or False on failure.
188 | */
189 | public function delete($key)
190 | {
191 | $key = $this->mapKey($key);
192 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
193 | if (!file_exists($path)) {
194 | return false;
195 | }
196 |
197 | return unlink($path);
198 | }
199 |
200 | /**
201 | * Removes all the cached entries associated with the given tag names.
202 | *
203 | * @param array $tags The array of tags to remove.
204 | * @return boolean Returns True on success or False on failure.
205 | */
206 | public function clean(array $tags)
207 | {
208 | $toRemove = array();
209 | $cleaned = false;
210 |
211 | foreach ($tags as $tag) {
212 | $keys = $this->loadTag($tag);
213 | if (null === $keys) {
214 | continue;
215 | }
216 | $cleaned = true;
217 | $toRemove = array_merge($toRemove, $keys);
218 | }
219 | $toRemove = array_unique($toRemove);
220 |
221 | foreach ($toRemove as $key) {
222 | $this->delete($this->removePrefixKey($key));
223 | }
224 |
225 | return $cleaned;
226 | }
227 |
228 | /**
229 | * Flush all the cached entries.
230 | *
231 | * @param boolean $all Wether to flush the whole database, or (preferably)
232 | * the entries prefixed with prefix_key and prefix_tag.
233 | * @return boolean Returns True on success or False on failure.
234 | */
235 | public function flush($all = false)
236 | {
237 | $files = scandir($this->getOption('directory'));
238 | foreach ($files as $file) {
239 | if ('.' === substr($file, 0, 1)) {
240 | continue;
241 | }
242 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
243 | $fullKey = base64_decode($file);
244 | $key = $this->removePrefixKey($fullKey);
245 |
246 | if ($all || (!$all && ($key !== $fullKey || '' === $this->options['prefix_key']))) {
247 | unlink($path);
248 | }
249 | }
250 | }
251 |
252 | /**
253 | * Returns the time-to-live (in seconds) for the given key.
254 | *
255 | * @param string $key The name of the key.
256 | * @return int|false Returns the number of seconds left, 0 if valid
257 | * forever or False if the key is non-existant.
258 | */
259 | public function getTtl($key)
260 | {
261 | $key = $this->mapKey($key);
262 | $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
263 | if (!file_exists($path) || !is_file($path)) {
264 | return false;
265 | }
266 |
267 | $expire = $this->readFile($path, 2);
268 |
269 | if (0 === (int) $expire) {
270 | return 0;
271 | }
272 |
273 | return $expire - time();
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/Indexer/AbstractIndexer.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Indexer;
14 |
15 | /**
16 | * Base class provides the cache wrappers structure.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | abstract class AbstractIndexer implements Adapter
21 | {
22 |
23 | /**
24 | * Holds the name of the index.
25 | * @var string
26 | */
27 | protected $name;
28 |
29 | /**
30 | * Holds the index items.
31 | * @var array
32 | */
33 | protected $items = array();
34 |
35 | /**
36 | * Returns the index name.
37 | *
38 | * @return string a string.
39 | */
40 | public function getName()
41 | {
42 | return $this->name;
43 | }
44 |
45 | /**
46 | * Returns the items index.
47 | *
48 | * @return Returns an array of items or null failure.
49 | */
50 | public function getItems()
51 | {
52 | return $this->items;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Indexer/Adapter.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Indexer;
14 |
15 | /**
16 | * The interface/adapter that the cache indexers must implement.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | interface Adapter
21 | {
22 |
23 | /**
24 | * Adds one or many element(s) to the index.
25 | *
26 | * @param array|string $elements The element(s) to remove from the index.
27 | * @return Returns TRUE on success or FALSE on failure.
28 | */
29 | public function add($elements);
30 |
31 | /**
32 | * Removes one or many element(s) from the index.
33 | *
34 | * @param array|string $elements The element(s) to remove from the index.
35 | * @return Returns True on success or False on failure.
36 | */
37 | public function remove($elements);
38 |
39 | /**
40 | * Returns the indexed items.
41 | *
42 | * @return Returns True on success or False on failure.
43 | */
44 | public function load();
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/Indexer/MemcachedIndexer.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Indexer;
14 |
15 | use Apix\Cache\Memcached;
16 | use Apix\Cache\Serializer;
17 |
18 | /**
19 | * Memcached index.
20 | *
21 | * @author Franck Cassedanne
22 | */
23 | class MemcachedIndexer extends AbstractIndexer
24 | {
25 |
26 | const DIRTINESS_THRESHOLD = 100;
27 |
28 | /**
29 | * Holds an instane of a serializer.
30 | * @var Serializer\Stringset
31 | */
32 | protected $serializer;
33 |
34 | /**
35 | * Holds the index store engine.
36 | * @var Memcached
37 | */
38 | protected $engine;
39 |
40 | /**
41 | * Constructor.
42 | *
43 | * @param string $name
44 | * @param Memcached $engine
45 | */
46 | public function __construct($name, Memcached $engine)
47 | {
48 | $this->name = $name;
49 | $this->engine = $engine;
50 |
51 | $this->serializer = new Serializer\Stringset();
52 | }
53 |
54 | /**
55 | * Gets the adapter.
56 | *
57 | * @return \Memcached
58 | */
59 | public function getAdapter()
60 | {
61 | return $this->engine->getAdapter();
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function add($elements)
68 | {
69 | $str = $this->serializer->serialize((array) $elements);
70 |
71 | $success = $this->getAdapter()->append($this->name, $str);
72 |
73 | if (false === $success) {
74 | $success = $this->getAdapter()->add($this->name, $str);
75 | }
76 |
77 | return (boolean) $success;
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function remove($elements)
84 | {
85 | $str = $this->serializer->serialize((array) $elements, '-');
86 |
87 | return (boolean) $this->getAdapter()->append($this->name, $str);
88 | }
89 |
90 | /**
91 | * Returns the indexed items.
92 | *
93 | * @return boolean Returns True on success or Null on failure.
94 | */
95 | public function load()
96 | {
97 | // &$cas_token holds the unique 64-bit float token generated
98 | // by memcache for the named item @see \Memcached::get
99 | $str = $this->engine->get($this->name, $cas_token);
100 |
101 | if (null === $str) {
102 | return null;
103 | }
104 | $this->items = $this->serializer->unserialize($str);
105 |
106 | if ($this->serializer->getDirtiness() > self::DIRTINESS_THRESHOLD) {
107 | $this->purge($cas_token);
108 | }
109 |
110 | return $this->items;
111 | }
112 |
113 | /**
114 | * Purge atomically the index.
115 | *
116 | * @param double|null $cas_token
117 | * @return float $cas_token The Memcache CAS token.
118 | */
119 | protected function purge($cas_token)
120 | {
121 | $str = $this->serializer->serialize($this->items);
122 |
123 | return $this->getAdapter()->cas($cas_token, $this->name, $str);
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/Mongo.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * Mongo cache wrapper.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | class Mongo extends AbstractCache
21 | {
22 |
23 | /**
24 | * Holds the array of TTLs.
25 | * @var array
26 | */
27 | protected $ttls = array();
28 |
29 | /**
30 | * Holds the MongoDB object
31 | * @var \MongoDB|Mongo\DatabaseAdapter
32 | */
33 | public $db;
34 |
35 | /**
36 | * Holds the MongoCollection object
37 | * @var \MongoCollection|Mongo\CollectionAdapter
38 | */
39 | public $collection;
40 |
41 | /**
42 | * Indicates the use of the legacy \MongoClient.
43 | * @var bool
44 | */
45 | private $is_legacy = false;
46 |
47 | /**
48 | * Constructor. Sets the Mongo DB adapter.
49 | *
50 | * @param \MongoClient|\MongoDB\Client $Mongo A Mongo client instance.
51 | * @param array $options Array of options.
52 | */
53 | public function __construct($Mongo, array $options=null)
54 | {
55 | if (!is_a($Mongo, '\MongoDB\Client') && !is_a($Mongo, '\MongoClient')) {
56 | throw new \InvalidArgumentException(
57 | 'Expected instance of "\MongoDB\Client" or "\MongoClient"'
58 | );
59 | }
60 |
61 | // default options
62 | $this->options['db_name'] = 'apix';
63 | $this->options['collection_name'] = 'cache';
64 | $this->options['object_serializer'] = 'php'; // null, php, json, igBinary.
65 |
66 | // Set the adapter and merge the user+default options
67 | parent::__construct($Mongo, $options);
68 |
69 | if (is_a($Mongo, '\MongoDB\Client')) {
70 | $this->is_legacy = false;
71 | $this->db = new Mongo\DatabaseAdapter(
72 | $this->adapter->{$this->options['db_name']}
73 | );
74 | } else {
75 | $this->is_legacy = true;
76 | $this->db = $this->adapter->selectDB($this->options['db_name']);
77 | }
78 |
79 | $this->collection = $this->db->createCollection(
80 | $this->options['collection_name'],
81 | array()
82 | );
83 |
84 | $this->collection->ensureIndex(
85 | array('key' => 1),
86 | array(
87 | 'unique' => true,
88 | 'dropDups' => true,
89 | // 'sparse' => true
90 | )
91 | );
92 |
93 | // Using MongoDB TTL collections (MongoDB 2.2+)
94 | $this->collection->ensureIndex(
95 | array('expire' => 1),
96 | array('expireAfterSeconds' => 1)
97 | );
98 |
99 | $this->setSerializer($this->options['object_serializer']);
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | public function loadKey($key)
106 | {
107 | $mKey = $this->mapKey($key);
108 | $cache = $this->get($mKey);
109 |
110 | // check expiration
111 | if ( null === $cache or (
112 | isset($cache['expire']) && (string) $cache['expire']->sec < time()
113 | )) {
114 | unset($this->ttls[$mKey]);
115 |
116 | return null;
117 | }
118 |
119 | return $cache['serialized']
120 | ? $this->serializer->unserialize(
121 | $this->is_legacy
122 | ? $cache['serialized']->bin
123 | : $cache['serialized']->getData())
124 | : $cache['data'];
125 | }
126 |
127 | /**
128 | * Retrieves the cache item for the given id.
129 | *
130 | * @param string $key The cache key to retrieve.
131 | * @return mixed|null Returns the cached data or null.
132 | */
133 | public function get($key)
134 | {
135 | $cache = $this->collection->findOne(
136 | array('key' => $key),
137 | array('data', 'expire', 'serialized')
138 | );
139 |
140 | if ($cache !== null) {
141 | $this->ttls[$key] = isset($cache['expire'])
142 | ? $cache['expire']->sec - time()
143 | : 0;
144 | }
145 |
146 | return $cache;
147 | }
148 |
149 | /**
150 | * {@inheritdoc}
151 | */
152 | public function loadTag($tag)
153 | {
154 | $cache = $this->collection->find(
155 | array('tags' => $this->mapTag($tag)),
156 | array('key')
157 | );
158 |
159 | $keys = array_map(
160 | function ($v) { return $v['key']; },
161 | array_values(iterator_to_array($cache))
162 | );
163 |
164 | return empty($keys) ? null : $keys;
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function save($data, $key, array $tags=null, $ttl=null)
171 | {
172 | $key = $this->mapKey($key);
173 |
174 | $cache = array('key' => $key, 'data' => null, 'serialized' => null);
175 |
176 | if (null !== $this->serializer && (is_object($data) || is_array($data))) {
177 | $cache['serialized'] =
178 | $this->is_legacy
179 | ? new \MongoBinData($this->serializer->serialize($data), \MongoBinData::BYTE_ARRAY)
180 | : new \MongoDB\BSON\Binary($this->serializer->serialize($data), \MongoDB\BSON\Binary::TYPE_GENERIC);
181 | } else {
182 | $cache['data'] = $data;
183 | }
184 |
185 | if ($this->options['tag_enable'] && null !== $tags) {
186 | $cache['tags'] = array();
187 | foreach ($tags as $tag) {
188 | $cache['tags'][] = $this->mapTag($tag);
189 | }
190 | }
191 |
192 | $this->ttls[$key] = 0;
193 |
194 | if (null !== $ttl && 0 !== $ttl) {
195 | $expire = time()+$ttl;
196 |
197 | $cache['expire'] = $this->is_legacy
198 | ? new \MongoDate($expire)
199 | : new \MongoDB\BSON\UTCDateTime($expire * 1000);
200 |
201 | $this->ttls[$key] = $ttl;
202 | }
203 |
204 | $res = $this->collection->update(
205 | array('key' => $key), $cache, array('upsert' => true)
206 | );
207 |
208 | return (boolean) $res['ok'];
209 | }
210 |
211 | /**
212 | * {@inheritdoc}
213 | */
214 | public function clean(array $tags)
215 | {
216 | $items = array();
217 | foreach ($tags as $tag) {
218 | $items += (array) $this->loadTag($tag);
219 | }
220 | $res = $this->collection->remove(
221 | array('key'=>array('$in'=>$items))
222 | );
223 |
224 | return (boolean) $res['n'];
225 | }
226 |
227 | /**
228 | * {@inheritdoc}
229 | */
230 | public function delete($key)
231 | {
232 | $res = $this->collection->remove(
233 | array('key' => $this->mapKey($key))
234 | );
235 |
236 | return (boolean) $res['n'];
237 | }
238 |
239 | /**
240 | * {@inheritdoc}
241 | */
242 | public function flush($all=false)
243 | {
244 | if (true === $all) {
245 | $res = $this->collection->drop();
246 | return (boolean) $res['ok'];
247 | }
248 |
249 | $regex = $this->is_legacy
250 | ? new \MongoRegex('/^' . $this->mapKey('') . '/')
251 | : array('$regex' => '^' . $this->mapKey(''));
252 |
253 | $res = $this->collection->remove( array('key' => $regex) );
254 |
255 | return (boolean) $res['ok'];
256 | }
257 |
258 | /**
259 | * Counts the number of cached items.
260 | *
261 | * @return integer Returns the number of items in the cache.
262 | */
263 | public function count()
264 | {
265 | return (integer) $this->collection->count();
266 | }
267 |
268 | /**
269 | * {@inheritdoc}
270 | */
271 | public function getTtl($key)
272 | {
273 | $mKey = $this->mapKey($key);
274 |
275 | return !isset($this->ttls[$mKey]) && null === $this->get($mKey)
276 | ? false
277 | : $this->ttls[$mKey];
278 | }
279 |
280 | }
281 |
--------------------------------------------------------------------------------
/src/Mongo/CollectionAdapter.php:
--------------------------------------------------------------------------------
1 | collection = $collection;
24 | }
25 |
26 | public function ensureIndex(array $keys, array $options = array())
27 | {
28 | $this->collection->createIndex($keys, $options);
29 | }
30 |
31 | public function insert($a, array $options = array())
32 | {
33 | try {
34 | $this->collection->insertOne($a, $options);
35 | return ['ok' => 1];
36 | } catch (\Exception $e) {
37 | return ['ok' => 0, 'error' => $e->getMessage()];
38 | }
39 | }
40 |
41 | public function update(array $criteria, array $newobj, array $options = array())
42 | {
43 | try {
44 | $this->collection->updateOne($criteria, array('$set' => $newobj), $options);
45 | return ['ok' => 1];
46 | } catch (\Exception $e) {
47 | return ['ok' => 0, 'error' => $e->getMessage()];
48 | }
49 | }
50 |
51 | public function remove(array $criteria = array(), array $options = array())
52 | {
53 | try {
54 | $result = $this->collection->deleteMany($criteria, $options);
55 | return ['ok' => 1, 'n' => $result->getDeletedCount()];
56 | } catch (\Exception $e) {
57 | return ['ok' => 0, 'error' => $e->getMessage()];
58 | }
59 | }
60 |
61 | public function find(array $query = array(), array $fields = array())
62 | {
63 | $options = ['projection' => array_fill_keys($fields, 1) + ['_id' => 0]];
64 |
65 | return $this->collection->find($query, $options);
66 | }
67 |
68 | public function findOne(array $query = array(), array $fields = array())
69 | {
70 | $options = ['projection' => array_fill_keys($fields, 1) + ['_id' => 0]];
71 |
72 | $result = $this->collection->findOne($query, $options);
73 |
74 | // mimic \MongoDate->sec
75 | if (!empty($result['expire'])) {
76 | $sec = $result['expire']->toDateTime()->getTimestamp();
77 | $result['expire'] = new \stdClass();
78 | $result['expire']->sec = $sec;
79 | }
80 |
81 | return $result;
82 | }
83 |
84 | public function drop()
85 | {
86 | $this->collection->drop();
87 | return ['ok' => 1];
88 | }
89 |
90 | public function count($query = array())
91 | {
92 | return $this->collection->count($query);
93 | }
94 | }
--------------------------------------------------------------------------------
/src/Mongo/DatabaseAdapter.php:
--------------------------------------------------------------------------------
1 | db = $db;
23 | }
24 |
25 | /**
26 | * @param string $name
27 | * @param array $options
28 | * @return CollectionAdapter
29 | */
30 | public function createCollection($name, $options)
31 | {
32 | return new CollectionAdapter($this->db->selectCollection($name, $options));
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Pdo/Mysql.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Pdo;
14 |
15 | use Apix\Cache\AbstractPdo;
16 |
17 | /**
18 | * The Mysql (PDO) wrapper.
19 | *
20 | * @author Franck Cassedanne
21 | */
22 | class Mysql extends AbstractPdo
23 | {
24 |
25 | /**
26 | * Holds the SQL definitions for MySQL 3.x, 4.x and 5.x.
27 | */
28 | protected $sql_definitions = array(
29 | 'init' => 'CREATE TABLE IF NOT EXISTS %s (`key` VARCHAR(255) NOT NULL,
30 | `data` LONGTEXT NULL, `tags` TEXT NULL, `expire` INTEGER
31 | UNSIGNED, `dated` TIMESTAMP, PRIMARY KEY (`key`))
32 | ENGINE=MYISAM DEFAULT charset=utf8;',
33 | 'key_idx' => 'CREATE INDEX `%s_key_idx` ON `%s` (`key`);',
34 | 'exp_idx' => 'CREATE INDEX `%s_exp_idx` ON `%s` (`expire`);',
35 |
36 | // 'tag_idx' will throw MYSQL ERROR 1170 -- if the index is needed then
37 | // we should split keys and tags into diff tables and use varchar(255).
38 | // 'tag_idx' => 'CREATE INDEX `%s_tag_idx` ON `%s` (`tags`);',
39 |
40 | 'loadKey' => 'SELECT `data`, `expire` FROM `%s` WHERE `key`=:key
41 | AND (`expire` IS NULL OR `expire` > :now);',
42 | 'loadTag' => 'SELECT `key` FROM `%s` WHERE `tags` LIKE :tag
43 | AND (`expire` IS NULL OR `expire` > :now);',
44 | 'update' => 'UPDATE `%s` SET `data`=:data, `tags`=:tags,
45 | `expire`=:exp, `dated`=:dated WHERE `key`=:key;',
46 | 'insert' => 'INSERT INTO `%s` (`key`, `data`, `tags`, `expire`,
47 | `dated`) VALUES (:key, :data, :tags, :exp, :dated)
48 | ON DUPLICATE KEY UPDATE `data`=VALUES(`data`),
49 | `tags`=VALUES(`tags`), `expire`=VALUES(`expire`);',
50 | 'delete' => 'DELETE FROM `%s` WHERE `key`=?;',
51 | 'clean' => 'DELETE FROM `%s` WHERE %s;', // %s 'clean_like' iterated
52 | 'clean_like'=> 'tags LIKE ?',
53 | 'flush_all' => 'DELETE FROM `%s`;',
54 | 'flush' => 'DELETE FROM `%s` WHERE `key` LIKE ? OR `tags` LIKE ?;',
55 | 'purge' => 'DELETE FROM `%s` WHERE `expire` IS NOT NULL AND `expire` < %d;'
56 | );
57 |
58 | /**
59 | * Constructor.
60 | *
61 | * @param \PDO $pdo
62 | * @param array $options Array of options.
63 | */
64 | public function __construct(\PDO $pdo, array $options=null)
65 | {
66 | parent::__construct($pdo, $options);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/Pdo/Pgsql.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Pdo;
14 |
15 | use Apix\Cache\AbstractPdo;
16 |
17 | /**
18 | * The PostgreSQL (PDO) wrapper.
19 | *
20 | * @author Franck Cassedanne
21 | */
22 | class Pgsql extends AbstractPdo
23 | {
24 |
25 | /**
26 | * Holds the SQL definitions for PostgreSQL.
27 | */
28 | protected $sql_definitions = array(
29 | 'init' => 'CREATE TABLE IF NOT EXISTS "%s"
30 | ("key" VARCHAR PRIMARY KEY, "data" TEXT, "tags" TEXT,
31 | "expire" INTEGER, "dated" TIMESTAMP);',
32 | 'key_idx' => 'CREATE INDEX "%s_key_idx" ON "%s" ("key");',
33 | 'exp_idx' => 'CREATE INDEX "%s_exp_idx" ON "%s" ("expire");',
34 | 'tag_idx' => 'CREATE INDEX "%s_tag_idx" ON "%s" ("tags");',
35 | 'loadKey' => 'SELECT "data", "expire" FROM "%s" WHERE "key"=:key AND
36 | ("expire" IS NULL OR "expire" > :now);',
37 | 'loadTag' => 'SELECT "key" FROM "%s" WHERE "tags" LIKE :tag AND
38 | ("expire" IS NULL OR "expire" > :now);',
39 | 'update' => 'UPDATE "%s" SET "data"=:data, "tags"=:tags, "expire"=:exp,
40 | "dated"=:dated WHERE "key"=:key;',
41 | 'insert' => 'INSERT INTO "%s" ("key", "data", "tags", "expire", "dated")
42 | VALUES (:key, :data, :tags, :exp, :dated);',
43 | 'delete' => 'DELETE FROM "%s" WHERE "key"=?;',
44 | 'clean' => 'DELETE FROM "%s" WHERE %s;', // %s 'clean_like' iterated
45 | 'clean_like'=> 'tags LIKE ?',
46 | 'flush_all' => 'DELETE FROM "%s";',
47 | 'flush' => 'DELETE FROM "%s" WHERE "key" LIKE ? OR "tags" LIKE ?;',
48 | 'purge' => 'DELETE FROM "%s" WHERE "expire" IS NOT NULL AND "expire" < %d;'
49 | );
50 |
51 | /**
52 | * Constructor.
53 | *
54 | * @param \PDO $pdo
55 | * @param array $options Array of options.
56 | */
57 | public function __construct(\PDO $pdo, array $options=null)
58 | {
59 | parent::__construct($pdo, $options);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Pdo/Sql1999.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Pdo;
14 |
15 | use Apix\Cache\AbstractPdo;
16 |
17 | /**
18 | * The SQL:1999 / SQL 3 (PDO) cache wrapper.
19 | *
20 | * Allows to use databases that have 'some' SQL:1999 compliance.
21 | * e.g. Oracle, IBM DB2, Informix.
22 | * PostgreSQL, MySQl and SQLite should also support SQL99.
23 | *
24 | * Conforms to at least SQL-92 are DB2, MSSQL, MySQL, Oracle, Informix.
25 | *
26 | * @author Franck Cassedanne
27 | */
28 | class Sql1999 extends AbstractPdo
29 | {
30 |
31 | /**
32 | * Holds a generic SQL-99'ish schema definitions.
33 | * @see http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
34 | */
35 | protected $sql_definitions = array(
36 | 'init' => 'CREATE TABLE "%s" ("key" VARCHAR PRIMARY KEY, "data" TEXT,
37 | "tags" TEXT, "expire" INTEGER, "dated" TIMESTAMP);',
38 | 'key_idx' => 'CREATE INDEX "%s_key_idx" ON "%s" ("key");',
39 | 'exp_idx' => 'CREATE INDEX "%s_exp_idx" ON "%s" ("expire");',
40 | 'tag_idx' => 'CREATE INDEX "%s_tag_idx" ON "%s" ("tags");',
41 | 'loadKey' => 'SELECT "data", "expire" FROM "%s" WHERE "key"=:key AND
42 | ("expire" IS NULL OR "expire" > :now);',
43 | 'loadTag' => 'SELECT "key" FROM "%s" WHERE "tags" LIKE :tag AND
44 | ("expire" IS NULL OR "expire" > :now);',
45 | 'update' => 'UPDATE "%s" SET "data"=:data, "tags"=:tags, "expire"=:exp,
46 | "dated"=:dated WHERE "key"=:key;',
47 | 'insert' => 'INSERT INTO "%s" ("key", "data", "tags", "expire", "dated")
48 | VALUES (:key, :data, :tags, :exp, :dated);',
49 | 'delete' => 'DELETE FROM "%s" WHERE "key"=?;',
50 | 'clean' => 'DELETE FROM "%s" WHERE %s;', // %s 'clean_like' iterated
51 | 'clean_like'=> 'tags LIKE ?',
52 | 'flush_all' => 'DELETE FROM "%s";',
53 | 'flush' => 'DELETE FROM %s WHERE key LIKE ? OR tags LIKE ?;',
54 | 'purge' => 'DELETE FROM "%s" WHERE "expire" IS NOT NULL AND "expire" < %d;'
55 | );
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Pdo/Sqlite.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Pdo;
14 |
15 | use Apix\Cache\AbstractPdo;
16 |
17 | /**
18 | * The SQLite (PDO) wrapper.
19 | *
20 | * @author Franck Cassedanne
21 | */
22 | class Sqlite extends AbstractPdo
23 | {
24 |
25 | /**
26 | * Holds the SQL definitions for SQLite v2/v3.
27 | */
28 | protected $sql_definitions = array(
29 | 'init' => 'CREATE TABLE IF NOT EXISTS %s (key VARCHAR PRIMARY KEY,
30 | data TEXT, tags TEXT, expire INTEGER, dated TIMESTAMP);',
31 | 'key_idx' => 'CREATE INDEX IF NOT EXISTS %s_key_idx ON %s (key);',
32 | 'exp_idx' => 'CREATE INDEX IF NOT EXISTS %s_exp_idx ON %s (expire);',
33 | 'tag_idx' => 'CREATE INDEX IF NOT EXISTS %s_tag_idx ON %s (tags);',
34 | 'loadKey' => 'SELECT data, expire FROM %s WHERE key=:key AND
35 | (expire IS NULL OR expire > :now);',
36 | 'loadTag' => 'SELECT key FROM %s WHERE tags LIKE :tag AND
37 | (expire IS NULL OR expire > :now);',
38 | 'update' => 'UPDATE %s SET data=:data, tags=:tags, expire=:exp,
39 | dated=:dated WHERE key=:key;',
40 | 'insert' => 'INSERT INTO %s (key, data, tags, expire, dated)
41 | VALUES (:key, :data, :tags, :exp, :dated);',
42 | 'delete' => 'DELETE FROM %s WHERE key = ?;',
43 | 'clean' => 'DELETE FROM %s WHERE %s;', // %s 'clean_like' iterated
44 | 'clean_like'=> 'tags LIKE ?',
45 | 'flush_all' => 'DELETE FROM %s;',
46 | 'flush' => 'DELETE FROM %s WHERE key LIKE ? OR tags LIKE ?;',
47 | 'purge' => 'DELETE FROM %s WHERE expire IS NOT NULL AND expire < %d;'
48 | );
49 |
50 | /**
51 | * Constructor.
52 | *
53 | * @param \PDO $pdo
54 | * @param array $options Array of options.
55 | */
56 | public function __construct(\PDO $pdo, array $options=null)
57 | {
58 | // Set some SQLite PRAGMA to speed things up.
59 | // @see http://www.sqlite.org/pragma.html
60 | // @see http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
61 | $pdo->exec('PRAGMA synchronous=OFF');
62 | $pdo->exec('PRAGMA journal_mode=MEMORY');
63 | $pdo->exec('PRAGMA temp_store=MEMORY');
64 | $pdo->exec('PRAGMA count_changes=false');
65 |
66 | parent::__construct($pdo, $options);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/PsrCache/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\PsrCache;
14 |
15 | /**
16 | * Exception for invalid cache arguements.
17 | */
18 | class InvalidArgumentException
19 | extends \InvalidArgumentException
20 | implements \Psr\Cache\InvalidArgumentException
21 | { }
22 |
--------------------------------------------------------------------------------
/src/PsrCache/Item.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\PsrCache;
14 |
15 | use Psr\Cache\CacheItemInterface as ItemInterface;
16 | use Psr\Cache\CacheItemPoolInterface as ItemPoolInterface;
17 |
18 | class Item implements ItemInterface
19 | {
20 | const DEFAULT_EXPIRATION = 'now +1 year';
21 |
22 | /**
23 | * The cache key for the item.
24 | * @var string
25 | */
26 | protected $key;
27 |
28 | /**
29 | * The raw (unserialized) cached value.
30 | * @var mixed
31 | */
32 | protected $value;
33 |
34 | /**
35 | * Wether the item has been saved to the cache yet.
36 | * @var bool
37 | */
38 | protected $hit = false;
39 |
40 | /**
41 | * The expiration date.
42 | * @var \DateTime
43 | */
44 | protected $expiration;
45 |
46 | /**
47 | * Constructs a new Item.
48 | * You should never use this directly. It is used internally to create items
49 | * from the pool.
50 | * @param string $key The item key
51 | * @param mixed $value The item value (unserialized)
52 | * @param \DateTime|integer|null $ttl
53 | * @param bool $hit Was this item retrived from cache?
54 | */
55 | public function __construct($key, $value = null, $ttl = null, $hit = false)
56 | {
57 | $this->key = self::normalizedKey($key);
58 | $this->value = $value;
59 | $this->hit = $hit;
60 | $this->expiresAt($ttl);
61 | }
62 |
63 | /**
64 | * Returns a normalised key.
65 | *
66 | * @return string
67 | */
68 | public static function normalizedKey($key)
69 | {
70 | if (!is_string($key) || empty($key) || strpbrk($key, '{}()/\@:') ) {
71 | throw new InvalidArgumentException(
72 | 'Item key (' . var_export($key, true) . ') is invalid.'
73 | );
74 | }
75 |
76 | return $key;
77 | }
78 |
79 | /**
80 | * {@inheritdoc}
81 | */
82 | public function getKey()
83 | {
84 | return $this->key;
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function get()
91 | {
92 | return $this->hit ? $this->value : null;
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | public function set($value = null)
99 | {
100 | $this->value = $value;
101 | $this->hit = false;
102 |
103 | return $this;
104 | }
105 |
106 | /**
107 | * {@inheritdoc}
108 | */
109 | public function isHit()
110 | {
111 | return $this->hit && $this->getTtlInSecond() > 0;
112 | }
113 |
114 | /**
115 | * {@inheritdoc}
116 | */
117 | public function getExpiration()
118 | {
119 | return $this->expiration;
120 | }
121 |
122 | /**
123 | * Returns the time to live in second.
124 | *
125 | * @return integer
126 | */
127 | public function getTtlInSecond()
128 | {
129 | return $this->expiration->format('U') - time();
130 | }
131 |
132 | /**
133 | * Sets the cache hit for this item.
134 | *
135 | * @param boolean $hit
136 | * @return static The invoked object.
137 | */
138 | public function setHit($hit)
139 | {
140 | $this->hit = (bool) $hit;
141 |
142 | return $this;
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function expiresAt($expiration = null)
149 | {
150 | if ($expiration instanceof \DateTime) {
151 | $this->expiration = $expiration;
152 |
153 | } elseif (is_int($expiration)) {
154 | $this->expiration = new \DateTime(
155 | 'now +' . $expiration . ' seconds'
156 | );
157 |
158 | } elseif (null === $expiration) {
159 | $this->expiration = new \DateTime(self::DEFAULT_EXPIRATION);
160 |
161 | } else {
162 |
163 | throw new InvalidArgumentException(
164 | 'Integer or \DateTime object expected.'
165 | );
166 | }
167 |
168 | return $this;
169 | }
170 |
171 | /**
172 | * {@inheritdoc}
173 | */
174 | public function expiresAfter($time)
175 | {
176 | if ($time instanceof \DateInterval) {
177 | $this->expiration = new \DateTime();
178 | $this->expiration->add($time);
179 |
180 | } elseif (is_int($time)) {
181 | $this->expiration = new \DateTime('now +' . $time . ' seconds');
182 |
183 | } elseif (null === $time) {
184 | $this->expiration = new \DateTime(self::DEFAULT_EXPIRATION);
185 |
186 | } else {
187 |
188 | throw new InvalidArgumentException(
189 | 'Integer or \DateInterval object expected.'
190 | );
191 | }
192 |
193 | return $this;
194 | }
195 |
196 | /**
197 | * Returns the item value.
198 | *
199 | * @return string
200 | */
201 | public function __toString()
202 | {
203 | return $this->value;
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/src/PsrCache/Pool.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\PsrCache;
14 |
15 | use Apix\Cache\Adapter as CacheAdapter;
16 | use Psr\Cache\CacheItemInterface as ItemInterface;
17 | use Psr\Cache\CacheItemPoolInterface as ItemPoolInterface;
18 |
19 | class Pool implements ItemPoolInterface
20 | {
21 |
22 | /**
23 | *
24 | * @var CacheAdapter
25 | */
26 | protected $cache_adapter;
27 |
28 | /**
29 | * Deferred cache items to be saved later.
30 | *
31 | * @var array Collection of \Apix\PsrCache\Item.
32 | */
33 | protected $deferred = array();
34 |
35 | /**
36 | * Constructor.
37 | */
38 | public function __construct(CacheAdapter $cache_adapter)
39 | {
40 | $this->cache_adapter = $cache_adapter;
41 |
42 | $options = array(
43 | 'tag_enable' => false // wether to enable tagging
44 | );
45 | $this->cache_adapter->setOptions($options);
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function getItem($key)
52 | {
53 | $key = Item::normalizedKey($key);
54 |
55 | if (isset($this->deferred[$key])) {
56 | return $this->deferred[$key];
57 | }
58 |
59 | $value = $this->cache_adapter->loadKey($key);
60 |
61 | return new Item(
62 | $this->cache_adapter->removePrefixKey($key),
63 | $value,
64 | $this->cache_adapter->getTtl($key) ?: null,
65 | (bool) $value // indicates wether it is loaded from cache or not.
66 | );
67 | }
68 |
69 | /**
70 | * {@inheritdoc}
71 | */
72 | public function getItems(array $keys = array())
73 | {
74 | $items = array();
75 | foreach ($keys as $key) {
76 | $items[$key] = $this->getItem($key);
77 | }
78 |
79 | return $items;
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function hasItem($key)
86 | {
87 | return $this->getItem($key)->isHit();
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function clear()
94 | {
95 | $this->deferred = array();
96 | return $this->cache_adapter->flush(true);
97 | }
98 |
99 | /**
100 | * {@inheritdoc}
101 | */
102 | public function deleteItems(array $keys)
103 | {
104 | $checks = array();
105 | foreach ($keys as $key) {
106 | // Only delete from cache if it actually exists
107 | if($this->getItem($key)->isHit()) {
108 | $checks[] = $this->cache_adapter->delete($key);
109 | }
110 | unset($this->deferred[$key]);
111 | }
112 | return (bool) !in_array(false, $checks, true);
113 | }
114 |
115 | /**
116 | * {@inheritdoc}
117 | */
118 | public function deleteItem($key)
119 | {
120 | return $this->deleteItems(array($key));
121 | }
122 |
123 | /**
124 | * {@inheritdoc}
125 | */
126 | public function save(ItemInterface $item)
127 | {
128 | $ttl = $item->getTtlInSecond();
129 |
130 | $item->setHit(true);
131 | $success = $this->cache_adapter->save(
132 | $item->get(), // value to store
133 | $item->getKey(), // its key
134 | null, // disable tags support
135 | is_null($ttl) ? 0 : $ttl // ttl in sec or null forever
136 | );
137 | $item->setHit($success);
138 |
139 | return $success;
140 | }
141 |
142 | /**
143 | * {@inheritdoc}
144 | */
145 | public function saveDeferred(ItemInterface $item)
146 | {
147 | $this->deferred[$item->getKey()] = $item;
148 |
149 | return $this;
150 | }
151 |
152 | /**
153 | * {@inheritdoc}
154 | */
155 | public function commit()
156 | {
157 | foreach ($this->deferred as $key => $item) {
158 | $this->save($item);
159 | if ( $item->isHit() ) {
160 | unset($this->deferred[$key]);
161 | }
162 | }
163 |
164 | return empty($this->deferred);
165 | }
166 |
167 | /**
168 | * Returns the cache adapter for this pool.
169 | *
170 | * @return CacheAdapter
171 | */
172 | public function getCacheAdapter()
173 | {
174 | return $this->cache_adapter;
175 | }
176 |
177 | /**
178 | * Commit the deferred items ~ acts as the last resort garbage collector.
179 | */
180 | public function __destruct()
181 | {
182 | $this->commit();
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/src/PsrCache/TaggableItem.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\PsrCache;
14 |
15 | class TaggableItem extends Item
16 | {
17 |
18 | /**
19 | * The tags associated with this entry.
20 | * @var array|null
21 | */
22 | protected $tags = null;
23 |
24 | /**
25 | * Sets this item tags.
26 | *
27 | * @param array|null $tags
28 | * @return TaggableItem The invoked object.
29 | */
30 | public function setTags(array $tags=null)
31 | {
32 | $this->tags = $tags;
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * Returns this item tags.
39 | *
40 | * @return array|null
41 | */
42 | public function getTags()
43 | {
44 | return $this->tags;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/PsrCache/TaggablePool.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\PsrCache;
14 |
15 | use Apix\Cache\Adapter as CacheAdapter;
16 | use Psr\Cache\CacheItemInterface as ItemInterface;
17 |
18 | class TaggablePool extends Pool
19 | {
20 |
21 | /**
22 | * The tags associated with this pool (just an optimisation hack).
23 | * @var array|null
24 | */
25 | private $_tags = null;
26 |
27 | /**
28 | * Constructor.
29 | */
30 | public function __construct(CacheAdapter $cache_adapter)
31 | {
32 | $this->cache_adapter = $cache_adapter;
33 |
34 | $options = array(
35 | 'tag_enable' => true // wether to enable tagging
36 | );
37 | $this->cache_adapter->setOptions($options);
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function getItem($key)
44 | {
45 | $value = $this->cache_adapter->loadKey($key);
46 |
47 | $item = new TaggableItem(
48 | $this->cache_adapter->removePrefixKey($key),
49 | $value,
50 | $this->cache_adapter->getTtl($key) ?: null,
51 | (bool) $value // indicates wether it is loaded from cache or not.
52 | );
53 |
54 | // Set this pool tags rather than the actual cached item tags.
55 | $item->setTags($this->_tags);
56 |
57 | return $item;
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function save(ItemInterface $item)
64 | {
65 | $ttl = $item->getTtlInSecond();
66 | $this->_tags = $item->getTags();
67 |
68 | $item->setHit(true);
69 | $success = $this->cache_adapter->save(
70 | $item->get(), // value to store
71 | $item->getKey(), // its key
72 | $this->_tags, // this pool tags
73 | is_null($ttl) ? 0 : $ttl // ttl in sec or null for ever
74 | );
75 | $item->setHit($success);
76 |
77 | return $success;
78 | }
79 |
80 | /**
81 | * Retrieves the cache keys for the given tag.
82 | *
83 | * @param string $tag The cache tag to retrieve.
84 | * @return array Returns an array of cache keys.
85 | */
86 | public function getItemsByTag($tag)
87 | {
88 | $keys = $this->cache_adapter->loadTag($tag);
89 | $items = array();
90 | if ($keys) {
91 | foreach ($keys as $key) {
92 | $k = $this->cache_adapter->removePrefixKey($key);
93 | $items[$k] = $this->getItem($k);
94 | }
95 | }
96 |
97 | return $items;
98 | }
99 |
100 | /**
101 | * Removes all the cached entries associated with the given tag names.
102 | *
103 | * @param array $tags The array of tags to remove.
104 | * @return bool
105 | */
106 | public function clearByTags(array $tags)
107 | {
108 | return $this->cache_adapter->clean($tags);
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/Redis.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache;
14 |
15 | /**
16 | * Redis/PhpRedis cache wrapper.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | class Redis extends AbstractCache
21 | {
22 |
23 | /**
24 | * Constructor.
25 | *
26 | * @param \Redis $redis A Redis client instance.
27 | * @param array $options Array of options.
28 | */
29 | public function __construct(\Redis $redis, array $options=null)
30 | {
31 | $options['atomicity'] = !isset($options['atomicity'])
32 | || true === $options['atomicity']
33 | ? \Redis::MULTI
34 | : \Redis::PIPELINE;
35 |
36 | $this->options['serializer'] = 'php'; // null, php, igBinary, json,
37 | // msgpack
38 |
39 | parent::__construct($redis, $options);
40 |
41 | $this->setSerializer($this->options['serializer']);
42 | $redis->setOption( \Redis::OPT_SERIALIZER, $this->getSerializer() );
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function loadKey($key)
49 | {
50 | $cache = $this->adapter->get($this->mapKey($key));
51 |
52 | return false === $cache ? null : $cache;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function loadTag($tag)
59 | {
60 | $cache = $this->adapter->sMembers($this->mapTag($tag));
61 |
62 | return empty($cache) ? null : $cache;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function save($data, $key, array $tags=null, $ttl=null)
69 | {
70 | $key = $this->mapKey($key);
71 |
72 | if (null === $ttl || 0 === $ttl) {
73 | $success = $this->adapter->set($key, $data);
74 | } else {
75 | $success = $this->adapter->setex($key, $ttl, $data);
76 | }
77 |
78 | if ($success && $this->options['tag_enable'] && !empty($tags)) {
79 | $redis = $this->adapter->multi($this->options['atomicity']);
80 | foreach ($tags as $tag) {
81 | $redis->sAdd($this->mapTag($tag), $key);
82 | }
83 | $redis->exec();
84 | }
85 |
86 | return $success;
87 | }
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public function clean(array $tags)
93 | {
94 | $items = array();
95 | foreach ($tags as $tag) {
96 | $keys = $this->loadTag($tag);
97 | if (is_array($keys)) {
98 | array_walk_recursive(
99 | $keys,
100 | function ($key) use (&$items) { $items[] = $key; }
101 | );
102 | }
103 | $items[] = $this->mapTag($tag);
104 | }
105 |
106 | return (boolean) $this->adapter->del($items);
107 | }
108 |
109 | /**
110 | * {@inheritdoc}
111 | */
112 | public function delete($key)
113 | {
114 | $key = $this->mapKey($key);
115 |
116 | if ($this->options['tag_enable']) {
117 | $tags = $this->adapter->keys($this->mapTag('*'));
118 | if (!empty($tags)) {
119 | $redis = $this->adapter->multi($this->options['atomicity']);
120 | foreach ($tags as $tag) {
121 | $redis->sRem($tag, $key);
122 | }
123 | $redis->exec();
124 | }
125 | }
126 |
127 | return (boolean) $this->adapter->del($key);
128 | }
129 |
130 | /**
131 | * {@inheritdoc}
132 | */
133 | public function flush($all=false)
134 | {
135 | if (true === $all) {
136 | return (boolean) $this->adapter->flushDb();
137 | }
138 | $items = array_merge(
139 | $this->adapter->keys($this->mapTag('*')),
140 | $this->adapter->keys($this->mapKey('*'))
141 | );
142 |
143 | return (boolean) $this->adapter->del($items);
144 | }
145 |
146 | /**
147 | * {@inheritdoc}
148 | * @param string $serializer
149 | */
150 | public function setSerializer($serializer)
151 | {
152 | switch ($serializer) {
153 | case 'json':
154 | // $this->serializer = \Redis::SERIALIZER_JSON;
155 | parent::setSerializer($serializer);
156 | break;
157 |
158 | case 'php':
159 | $this->serializer = \Redis::SERIALIZER_PHP;
160 | break;
161 |
162 | // @codeCoverageIgnoreStart
163 | case 'igBinary':
164 | // igBinary is not always compiled on the host machine.
165 | $this->serializer = \Redis::SERIALIZER_IGBINARY;
166 | break;
167 |
168 | case 'msgpack':
169 | // available on PHP7 since msgpack 2.0.1
170 | $this->serializer = \Redis::SERIALIZER_MSGPACK;
171 | break;
172 | // @codeCoverageIgnoreEnd
173 |
174 | default:
175 | $this->serializer = \Redis::SERIALIZER_NONE;
176 | }
177 | }
178 |
179 | /**
180 | * {@inheritdoc}
181 | */
182 | public function getTtl($key)
183 | {
184 | $ttl = $this->adapter->ttl($this->mapKey($key));
185 |
186 | if ($ttl == -2) {
187 | return false;
188 | }
189 |
190 | return $ttl > -1 ? $ttl : 0;
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/src/Runtime.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
9 | *
10 | */
11 |
12 | namespace Apix\Cache;
13 |
14 | /**
15 | * Runtime (Array/ArrayObject) cache wrapper.
16 | *
17 | * @author Franck Cassedanne
18 | */
19 | class Runtime extends AbstractCache
20 | {
21 | /**
22 | * Holds the cached items.
23 | * e.g. ['key' => ['data', 'tags', 'expire']]
24 | * @var array|\ArrayObject|\Traversable
25 | */
26 | protected $items;
27 |
28 | /**
29 | * Constructor.
30 | *
31 | * @param array|\Traversable|null $mix
32 | * @param array|null $options An array of user options.
33 | */
34 | public function __construct($mix = null, array $options=null)
35 | {
36 | if(null === $mix) {
37 | $this->items = new \ArrayObject();
38 | } else if(is_array($mix) || $mix instanceof \Traversable) {
39 | $this->items = $mix;
40 | } else {
41 | throw new \Apix\Cache\Exception("Error Processing Request");
42 | }
43 |
44 | parent::__construct(null, $options);
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function loadKey($key)
51 | {
52 | $key = $this->mapKey($key);
53 |
54 | return isset($this->items[$key]) ? $this->items[$key]['data'] : null;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function loadTag($tag)
61 | {
62 | if ($this->options['tag_enable']) {
63 | $tag = $this->mapTag($tag);
64 | $keys = array();
65 | foreach ($this->items as $key => $data) {
66 | if( isset($data['tags'])
67 | && false !== array_search($tag, $data['tags'])
68 | ) {
69 | $keys[] = $key;
70 | }
71 | }
72 |
73 | return empty($keys) ? null : $keys;
74 | }
75 | }
76 |
77 | /**
78 | * {@inheritdoc}
79 | */
80 | public function save($data, $key, array $tags=null, $ttl=null)
81 | {
82 | $key = $this->mapKey($key);
83 | $this->items[$key] = array(
84 | 'data' => $data,
85 | 'expire' => $ttl
86 | );
87 |
88 | if ($this->options['tag_enable'] && !empty($tags)) {
89 | $_tags = array();
90 | foreach ($tags as $tag) {
91 | $_tags[] = $this->mapTag($tag);
92 | }
93 | $this->items[$key]['tags'] = array_unique($_tags);
94 | }
95 |
96 | return true;
97 | }
98 |
99 | /**
100 | * {@inheritdoc}
101 | */
102 | public function delete($key)
103 | {
104 | $key = $this->mapKey($key);
105 |
106 | if ( isset($this->items[$key]) ) {
107 | unset($this->items[$key]);
108 |
109 | return true;
110 | }
111 |
112 | return false;
113 | }
114 |
115 | /**
116 | * {@inheritdoc}
117 | */
118 | public function clean(array $tags)
119 | {
120 | $rmed = array();
121 | foreach ($tags as $tag) {
122 | $keys = $this->loadTag($tag);
123 | if ($keys) {
124 | foreach ($keys as $key) {
125 | unset($this->items[$key]);
126 | }
127 | } else {
128 | $rmed[] = false;
129 | }
130 | }
131 |
132 | return !in_array(false, $rmed);
133 | }
134 |
135 | /**
136 | * {@inheritdoc}
137 | */
138 | public function flush($all=false)
139 | {
140 | $this->items = new \ArrayObject();
141 |
142 | return true;
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function getTtl($key)
149 | {
150 | $key = $this->mapKey($key);
151 |
152 | return isset($this->items[$key]) ? $this->items[$key]['expire'] : false;
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/Serializer/Adapter.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * The interface adapter for the cache serializer.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | interface Adapter
21 | {
22 |
23 | /**
24 | * Serialises mixed data as a string.
25 | *
26 | * @param mixed $data
27 | * @return string|mixed
28 | */
29 | public function serialize($data);
30 |
31 | /**
32 | * Unserialises a string representation as mixed data.
33 | *
34 | * @param string $str
35 | * @return mixed|string
36 | */
37 | public function unserialize($str);
38 |
39 | /**
40 | * Checks if the input is a serialized string representation.
41 | *
42 | * @param string $str
43 | * @return boolean
44 | * @deprecated
45 | */
46 | public function isSerialized($str);
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/Serializer/Igbinary.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * IgBinary - fast binary serializer.
17 | * Serializes cache data using the IgBinary extension.
18 | * @see https://github.com/IgBinary/IgBinary
19 | *
20 | * @author Franck Cassedanne
21 | */
22 | class Igbinary implements Adapter
23 | {
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function serialize($data)
29 | {
30 | return \igbinary_serialize($data);
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function unserialize($str)
37 | {
38 | return \igbinary_unserialize($str);
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | public function isSerialized($str)
45 | {
46 | if (!is_string($str)) {
47 | return false;
48 | }
49 |
50 | return @substr_count($str, "\000", 0, 3) == 3;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/Serializer/Json.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * Serializes data using the native PHP Json extension.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | class Json implements Adapter
21 | {
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function serialize($data)
27 | {
28 | return json_encode($data);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function unserialize($str)
35 | {
36 | return json_decode($str);
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function isSerialized($str)
43 | {
44 | if (!is_string($str)) {
45 | return false;
46 | }
47 |
48 | return (boolean) (json_decode($str) !== null);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Serializer/Msgpack.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * MessagePack - a light cross-language binary serializer.
17 | * Serializes data using the Msgpack extension.
18 | * @see https://github.com/msgpack/msgpack-php
19 | *
20 | * @author Franck Cassedanne
21 | */
22 | class Msgpack implements Adapter
23 | {
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function serialize($data)
29 | {
30 | return \msgpack_pack($data);
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function unserialize($str)
37 | {
38 | return \msgpack_unpack($str);
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | public function isSerialized($str)
45 | {
46 | if (!is_string($str)) {
47 | return false;
48 | }
49 |
50 | return (boolean) !is_integer( @\msgpack_unpack($str) );
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/Serializer/None.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * Blank/null/none serializer.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | class None implements Adapter
21 | {
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function serialize($data)
27 | {
28 | return $data;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function unserialize($str)
35 | {
36 | return $str;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function isSerialized($str)
43 | {
44 | return false;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Serializer/Php.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * Serializes data using the native PHP serializer.
17 | *
18 | * @author Franck Cassedanne
19 | */
20 | class Php implements Adapter
21 | {
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function serialize($data)
27 | {
28 | return serialize($data);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function unserialize($str)
35 | {
36 | return unserialize($str);
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function isSerialized($str)
43 | {
44 | if (!is_string($str)) {
45 | return false;
46 | }
47 |
48 | return (boolean) ($str=='b:0;' || @unserialize($str) !== false);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Serializer/Stringset.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\Serializer;
14 |
15 | /**
16 | * Serializes cache data using a stringset (appendset) such as:
17 | * 'tag1 tag2 -tag2 -tag3'
18 | *
19 | * @author Franck Cassedanne
20 | */
21 | class Stringset implements Adapter
22 | {
23 |
24 | /**
25 | * Holds this string dirtiness.
26 | * @var integer
27 | */
28 | protected $dirtiness;
29 |
30 | /**
31 | * {@inheritdoc}
32 | *
33 | * e.g. ['a','c'] => 'a b '
34 | */
35 | public function serialize($keys, $op='')
36 | {
37 | $str = '';
38 | foreach ((array) $keys as $key) {
39 | $str .= "${op}${key} ";
40 | }
41 |
42 | return $str != '' ? $str : null;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | *
48 | * e.g. 'a b c -b -x ' => ['a','c'];
49 | * Sets the dirtiness level (counts the negative entries).
50 | */
51 | public function unserialize($str)
52 | {
53 | $add = array();
54 | $remove = array();
55 | foreach (explode(' ', trim($str)) as $key) {
56 | if (isset($key[0])) {
57 | if ($key[0] == '-') {
58 | $remove[] = substr($key, 1);
59 | } else {
60 | $add[] = $key;
61 | }
62 | }
63 | }
64 |
65 | $this->dirtiness = count($remove);
66 |
67 | $items = array_values(array_diff($add, $remove));
68 |
69 | return empty($items) ? null : $items;
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function isSerialized($str)
76 | {
77 | if (!is_string($str)) {
78 | return false;
79 | }
80 |
81 | return (boolean) preg_match('/^.*\s$/', $str);
82 | }
83 |
84 | /**
85 | * Returns the dirtness level of the userialized string.
86 | *
87 | * @return integer
88 | */
89 | public function getDirtiness()
90 | {
91 | return $this->dirtiness;
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/tests/AbstractCacheTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | class AbstractCacheTest extends TestCase
18 | {
19 | protected $cache = null;
20 |
21 | public function setUp()
22 | {
23 | $this->cache = new Cache\Runtime(null, $this->options);
24 | }
25 |
26 | public function tearDown()
27 | {
28 | if (null !== $this->cache) {
29 | $this->cache->flush();
30 | unset($this->cache);
31 | }
32 | }
33 |
34 | public function testGetOption()
35 | {
36 | $this->assertSame(
37 | $this->options['prefix_key'],
38 | $this->cache->getOption('prefix_key')
39 | );
40 | }
41 |
42 | /**
43 | * @expectedException Apix\Cache\PsrCache\InvalidArgumentException
44 | */
45 | public function testGetOptionThrowAnInvalidArgumentException()
46 | {
47 | $this->cache->getOption('key');
48 | }
49 |
50 | public function testSetOption()
51 | {
52 | $this->cache->setOption('prefix_key', 'foo');
53 |
54 | $this->assertSame('foo', $this->cache->getOption('prefix_key'));
55 | }
56 |
57 | public function testGetAdapter()
58 | {
59 | $this->assertNull( $this->cache->getAdapter() );
60 | }
61 |
62 | public function testGetSetSerializer()
63 | {
64 | $this->cache->setSerializer(null);
65 | $this->assertNull( $this->cache->getSerializer() );
66 |
67 | $this->cache->setSerializer('none');
68 | $this->assertInstanceOf(
69 | '\Apix\Cache\Serializer\None',
70 | $this->cache->getSerializer()
71 | );
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/tests/ApcTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | /**
18 | * ApcTest, supports both APC and APCu user cache.
19 | *
20 | * usage: php -d apc.enable_cli=1 `which phpunit` -v tests/ApcTest.php
21 | *
22 | * @package Apix\Cache
23 | * @author Franck Cassedanne
24 | */
25 | class ApcTest extends GenericTestCase
26 | {
27 | protected $cache = null;
28 |
29 | public function setUp()
30 | {
31 | $this->skipIfMissing('apc');
32 |
33 | if (!ini_get('apc.enable_cli')) {
34 | self::markTestSkipped(
35 | 'apc.enable_cli MUST be enabled in order to run this unit test'
36 | );
37 | }
38 |
39 | $this->cache = new Cache\Apc($this->options);
40 | }
41 |
42 | public function tearDown()
43 | {
44 | if (null !== $this->cache) {
45 | $this->cache->flush();
46 | unset($this->cache);
47 | }
48 | }
49 |
50 | public function testComplyWithApc()
51 | {
52 | $this->assertTrue($this->cache->save('data', 'id'));
53 | $id = $this->cache->mapKey('id');
54 | $this->assertEquals('data', apc_fetch($id));
55 | }
56 |
57 | public function testFlushSelected()
58 | {
59 | $this->assertTrue(
60 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
61 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
62 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
63 | );
64 | apc_add('foo', 'bar');
65 | $this->assertTrue($this->cache->flush());
66 | $this->assertFalse($this->cache->flush());
67 | $this->assertEquals('bar', apc_fetch('foo'));
68 |
69 | $this->assertNull($this->cache->load('id3'));
70 | $this->assertNull($this->cache->load('tag1', 'tag'));
71 | }
72 |
73 | public function testFlushAll()
74 | {
75 | $this->assertTrue(
76 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
77 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
78 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
79 | );
80 |
81 | apc_add('foo', 'bar');
82 | $this->assertTrue($this->cache->flush(true)); // always true!
83 |
84 | $this->assertEquals(false, apc_fetch('foo'));
85 |
86 | $this->assertNull($this->cache->load('id3'));
87 | $this->assertNull($this->cache->load('tag1', 'tag'));
88 | }
89 |
90 | /**
91 | * -runTestsInSeparateProcesses
92 | * -runInSeparateProcesses
93 | * -preserveGlobalState enable
94 | * -depends test
95 | */
96 | public function testShortTtlDoesExpunge()
97 | {
98 | $this->markTestSkipped(
99 | "APC will only expunged its cache on the next request which makes "
100 | . "this specific unit untestable!?... :-("
101 | );
102 | $this->cache->save('ttl-1', 'ttlId', null, -1);
103 | // $this->assertSame('ttl-1', apc_fetch($this->cache->mapKey('ttlId')));
104 | // $this->assertSame('ttl-1', $this->cache->load('ttlId'));
105 | $this->assertNull( $this->cache->load('ttlId'), "Should be null");
106 | }
107 |
108 | public function testGetInternalInfos()
109 | {
110 | $this->cache->save('someData', 'someId', null, 69);
111 | $infos = $this->cache->getInternalInfos('someId');
112 | $this->assertSame(69, $infos['ttl']);
113 | }
114 |
115 | public function testGetInternalInfosReturnFalseWhenNonExistant()
116 | {
117 | $this->assertFalse(
118 | $this->cache->getInternalInfos('non-existant')
119 | );
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/tests/ApcuTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | /**
18 | * ApcTest, supports both APC and APCu user cache.
19 | *
20 | * usage: php -d apc.enable_cli=1 `which phpunit` -v tests/ApcTest.php
21 | *
22 | * @package Apix\Cache
23 | * @author Franck Cassedanne
24 | */
25 | class ApcuTest extends ApcTest
26 | {
27 | public function setUp()
28 | {
29 | $this->skipIfMissing('apcu');
30 |
31 | if (!ini_get('apc.enable_cli')) {
32 | self::markTestSkipped(
33 | 'apc.enable_cli MUST be enabled in order to run this unit test'
34 | );
35 | }
36 |
37 | $this->cache = new Cache\Apcu($this->options);
38 | }
39 |
40 | public function testComplyWithApc()
41 | {
42 | $this->assertTrue($this->cache->save('data', 'id'));
43 | $id = $this->cache->mapKey('id');
44 | $this->assertEquals('data', apcu_fetch($id));
45 | }
46 |
47 | public function testFlushSelected()
48 | {
49 | $this->assertTrue(
50 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
51 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
52 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
53 | );
54 | apcu_add('foo', 'bar');
55 | $this->assertTrue($this->cache->flush());
56 | $this->assertFalse($this->cache->flush());
57 | $this->assertEquals('bar', apcu_fetch('foo'));
58 |
59 | $this->assertNull($this->cache->load('id3'));
60 | $this->assertNull($this->cache->load('tag1', 'tag'));
61 | }
62 |
63 | public function testFlushAll()
64 | {
65 | $this->assertTrue(
66 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
67 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
68 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
69 | );
70 |
71 | apcu_add('foo', 'bar');
72 | $this->assertTrue($this->cache->flush(true)); // always true!
73 |
74 | $this->assertEquals(false, apcu_fetch('foo'));
75 |
76 | $this->assertNull($this->cache->load('id3'));
77 | $this->assertNull($this->cache->load('tag1', 'tag'));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/DirectoryTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | class DirectoryTest extends GenericTestCase
18 | {
19 | /** @var null|Cache\Directory */
20 | protected $cache = null;
21 |
22 | /** @var string **/
23 | protected $dir = null;
24 |
25 | public function setUp()
26 | {
27 | $this->dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'apix-cache-unittest';
28 |
29 | $this->cache = new Cache\Directory(
30 | $this->options + array('directory' => $this->dir)
31 | );
32 | }
33 |
34 | public function tearDown()
35 | {
36 | if (null !== $this->cache) {
37 | $this->cache->flush();
38 |
39 | $this->cache->delTree( $this->dir );
40 | unset($this->cache);
41 | }
42 | }
43 |
44 | public function testNonExistsExpire()
45 | {
46 | $this->assertTrue($this->cache->save('data', 'id', null, 1));
47 | $this->assertTrue($this->cache->save('data', 'id'));
48 |
49 | $this->assertEquals(0, $this->cache->getTtl('id'));
50 | }
51 |
52 | public function testDeleteWithNoTag()
53 | {
54 | $this->cache->setOption('tag_enable', false);
55 | $this->assertTrue($this->cache->save('data', 'id', array('tag')));
56 | $this->assertTrue($this->cache->delete('id'));
57 | }
58 |
59 | /**
60 | * Regression test for pull request GH#17
61 | *
62 | * @link https://github.com/frqnck/apix-cache/pull/17/files
63 | * "File and Directory Adapters @clean returns when fails to find a
64 | * key within a tag"
65 | * @see FilesTest\testPullRequest17()
66 | * @group pr
67 | */
68 | public function testPullRequest17()
69 | {
70 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'));
71 |
72 | $this->assertNotNull($this->cache->loadTag('tag2'));
73 | $this->assertTrue($this->cache->clean(array('non-existant', 'tag2')));
74 | $this->assertNull($this->cache->loadTag('tag2'));
75 |
76 | // for good measure.
77 | $this->assertFalse($this->cache->clean(array('tag2')));
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/tests/FactoryTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | class FactoryTest extends TestCase
18 | {
19 | protected $cache = null;
20 |
21 | public function setUp()
22 | {
23 | }
24 |
25 | public function tearDown()
26 | {
27 | if (null !== $this->cache) {
28 | $this->cache->flush();
29 | unset($this->cache);
30 | }
31 | }
32 |
33 | /**
34 | * @expectedException Apix\Cache\PsrCache\InvalidArgumentException
35 | */
36 | public function testPoolWithUnsurportedObjectThrowsException()
37 | {
38 | Cache\Factory::getPool( new \StdClass() );
39 | }
40 |
41 | public function testPoolFromCacheClientObject()
42 | {
43 | $adapter = new \ArrayObject();
44 | $pool = Cache\Factory::getPool($adapter, $this->options);
45 | $this->assertInstanceOf('\Apix\Cache\PsrCache\Pool', $pool);
46 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
47 | }
48 |
49 | /**
50 | * @expectedException Apix\Cache\PsrCache\InvalidArgumentException
51 | */
52 | public function testPoolWithUnsurportedStringThrowsException()
53 | {
54 | Cache\Factory::getPool('non-existant', $this->options);
55 | }
56 |
57 | public function testPoolFromString()
58 | {
59 | $pool = Cache\Factory::getPool('Runtime', $this->options);
60 | $this->assertInstanceOf('\Apix\Cache\PsrCache\Pool', $pool);
61 |
62 | $pool = Cache\Factory::getPool('Array', $this->options);
63 | $this->assertInstanceOf('\Apix\Cache\PsrCache\Pool', $pool);
64 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
65 | }
66 |
67 | public function testPoolFromStringMixedCase()
68 | {
69 | $pool = Cache\Factory::getPool('arRay', $this->options);
70 | $this->assertInstanceOf('\Apix\Cache\PsrCache\Pool', $pool);
71 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
72 | }
73 |
74 | public function testPoolFromArray()
75 | {
76 | $pool = Cache\Factory::getPool(array(), $this->options);
77 | $this->assertInstanceOf('\Apix\Cache\PsrCache\Pool', $pool);
78 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
79 | }
80 |
81 | public function testTaggablePoolFromString()
82 | {
83 | $pool = Cache\Factory::getPool('ArrayObject', $this->options, true);
84 | $this->assertInstanceOf('\Apix\Cache\PsrCache\TaggablePool', $pool);
85 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
86 | }
87 |
88 | public function testGetTaggablePool()
89 | {
90 | $pool = Cache\Factory::getTaggablePool(array(), $this->options, true);
91 | $this->assertInstanceOf('\Apix\Cache\PsrCache\TaggablePool', $pool);
92 | $this->assertInstanceOf('\Apix\Cache\Runtime', $pool->getCacheAdapter());
93 | }
94 |
95 | /**
96 | * @expectedException \Apix\Cache\Exception
97 | */
98 | public function testGetPoolThrowsApixCacheException()
99 | {
100 | $adapter = new Cache\Runtime(new \StdClass, $this->options);
101 | Cache\Factory::getPool($adapter);
102 | }
103 |
104 | public function providerAsPerExample()
105 | {
106 | return array(
107 | 'pdo client' => array(new \PDO('sqlite::memory:'), 'Pdo\Sqlite'),
108 | 'files' => array('files', 'Files'),
109 | 'Files adapter' => array(new Cache\Files(), 'Files'),
110 | 'directory' => array('Directory', 'Directory'),
111 | 'Directory adapter' => array(new Cache\Directory(), 'Directory'),
112 | 'apc' => array('apc', 'Apc'),
113 | 'apcu' => array('apcu', 'Apcu'),
114 | 'runtime' => array('runtime', 'Runtime'),
115 | 'array' => array(array(), 'Runtime'),
116 | );
117 | }
118 |
119 | /**
120 | * Regression test for bug GH#12
121 | *
122 | * @link https://github.com/frqnck/apix-cache/issues/12
123 | * "'Files' and 'Directory' are not listed as clients in the Factory"
124 | * @group regression
125 | * @dataProvider providerAsPerExample
126 | */
127 | public function testBugFactoryExample($backend, $expected)
128 | {
129 | $pool = Cache\Factory::getPool( $backend );
130 | $this->assertInstanceOf(
131 | '\Apix\Cache\\' . $expected,
132 | $pool->getCacheAdapter()
133 | );
134 | }
135 |
136 | /**
137 | * Regression test for bug GH#13
138 | *
139 | * @link https://github.com/frqnck/apix-cache/issues/13
140 | * "TaggablePool and Pool overrides prefix_key and prefix_tag options
141 | * with hardcoded values"
142 | * @group regression
143 | * @see PsrCache\PoolTest\testBug13()
144 | * @see PsrCache\TaggablePoolTest\testBug13()
145 | */
146 | public function testBug13()
147 | {
148 | $pool = Cache\Factory::getPool(array(), $this->options, true);
149 | $this->assertSame(
150 | $this->options['prefix_key'],
151 | $pool->getCacheAdapter()->getOption('prefix_key')
152 | );
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/tests/FilesTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | class FilesTest extends GenericTestCase
18 | {
19 | /** @var null|Cache\Files **/
20 | protected $cache = null;
21 |
22 | /** @var string **/
23 | protected $dir = null;
24 |
25 | public function setUp()
26 | {
27 | $this->dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'apix-cache-unittest';
28 |
29 | $this->cache = new Cache\Files(
30 | $this->options + array('directory' => $this->dir)
31 | );
32 | }
33 |
34 | public function tearDown()
35 | {
36 | if (null !== $this->cache) {
37 | $this->cache->flush();
38 | unset($this->cache);
39 |
40 | rmdir($this->dir);
41 | }
42 | }
43 |
44 | public function testCorrupted()
45 | {
46 | $this->assertTrue($this->cache->save('data', 'id'));
47 | $encoded = base64_encode($this->cache->mapKey('id'));
48 |
49 | file_put_contents($this->cache->getOption('directory').DIRECTORY_SEPARATOR.$encoded, '');
50 | $this->assertNull($this->cache->loadKey('id'));
51 |
52 | file_put_contents($this->cache->getOption('directory').DIRECTORY_SEPARATOR.$encoded, ' ');
53 | $this->assertNull($this->cache->loadKey('id'));
54 |
55 | file_put_contents($this->cache->getOption('directory').DIRECTORY_SEPARATOR.$encoded, ' '.PHP_EOL);
56 | $this->assertNull($this->cache->loadKey('id'));
57 |
58 | file_put_contents($this->cache->getOption('directory').DIRECTORY_SEPARATOR.$encoded, PHP_EOL);
59 | $this->assertNull($this->cache->loadKey('id'));
60 | }
61 |
62 | public function testExpired()
63 | {
64 | // 1 second ttl
65 | $this->cache->save('data', 'id1', null, 1);
66 | sleep(2);
67 | $this->assertNull($this->cache->loadKey('id1'));
68 |
69 | $this->cache->save('data', 'id2', null, 3);
70 | sleep(1);
71 | $this->assertEquals('data', $this->cache->loadKey('id2'));
72 |
73 | }
74 |
75 | public function testFlushAll()
76 | {
77 | $this->cache->save('testdata1', 'id1', array('tag1', 'tag2'));
78 | $this->cache->save('testdata2', 'id2', array('tag3', 'tag4'));
79 | $this->assertEquals(2, $this->getDirectoryFileCount($this->dir));
80 |
81 | $this->cache->flush(true);
82 | $this->assertEquals(0, $this->getDirectoryFileCount($this->dir));
83 | }
84 |
85 | /**
86 | * Regression test for pull request GH#17
87 | *
88 | * @link https://github.com/frqnck/apix-cache/pull/17/files
89 | * "File and Directory Adapters @clean returns when fails to find a
90 | * key within a tag"
91 | * @see DirectoryTest\testPullRequest17()
92 | * @group pr
93 | */
94 | public function testPullRequest17()
95 | {
96 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'));
97 |
98 | $this->assertNotNull($this->cache->loadTag('tag2'));
99 | $this->assertTrue($this->cache->clean(array('non-existant', 'tag2')));
100 | $this->assertNull($this->cache->loadTag('tag2'));
101 |
102 | // for good measure.
103 | $this->assertFalse($this->cache->clean(array('tag2')));
104 | }
105 |
106 | private function getDirectoryFileCount($folder){
107 | $iterator = new \FilesystemIterator($folder, \FilesystemIterator::SKIP_DOTS);
108 | return iterator_count($iterator);
109 | }
110 |
111 | }
--------------------------------------------------------------------------------
/tests/GenericTestCase.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | class GenericTestCase extends TestCase
16 | {
17 |
18 | public function testLoadKeyReturnsNullWhenInexistant()
19 | {
20 | $this->assertNull($this->cache->loadKey('id'));
21 | }
22 |
23 | public function testLoadTagReturnsNullWhenInexistant()
24 | {
25 | $this->assertNull($this->cache->loadTag('id'));
26 | }
27 |
28 | public function testSaveAndLoadWithString()
29 | {
30 | $this->assertTrue($this->cache->save('data', 'id'));
31 | $this->assertEquals('data', $this->cache->loadKey('id'));
32 | $this->assertEquals('data', $this->cache->load('id'));
33 | }
34 |
35 | public function testSaveAndLoadWithArray()
36 | {
37 | $data = array('foo' => 'bar');
38 | $this->assertTrue($this->cache->save($data, 'id'));
39 |
40 | $check = $this->cache->loadKey('id');
41 | if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
42 | $this->assertEquals($data, $check);
43 |
44 | $check = $this->cache->load('id');
45 | if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
46 | $this->assertEquals($data, $check);
47 | }
48 |
49 | public function testSaveAndLoadWithObject()
50 | {
51 | $data = new \stdClass();
52 | $this->assertTrue($this->cache->save($data, 'id'));
53 | $this->assertEquals($data, $this->cache->loadKey('id'));
54 | $this->assertEquals($data, $this->cache->load('id'));
55 | }
56 |
57 | public function testDeleteInexistantReturnsFalse()
58 | {
59 | $this->assertFalse($this->cache->delete('Inexistant'));
60 | }
61 |
62 | public function testDelete()
63 | {
64 | $this->assertTrue(
65 | $this->cache->save('foo value', 'foo')
66 | && $this->cache->save('bar value', 'bar')
67 | );
68 |
69 | $this->assertTrue($this->cache->delete('foo'));
70 | $this->assertFalse($this->cache->delete('foo'));
71 |
72 | $this->assertNull($this->cache->loadKey('foo'));
73 | }
74 |
75 | public function testTllFromSave()
76 | {
77 | $this->assertFalse($this->cache->getTtl('non-existant'));
78 |
79 | $this->assertTrue($this->cache->save('data', 'id'));
80 | $this->assertEquals(0, $this->cache->getTtl('id'),
81 | "Expiration should be set to 0 (for ever) by default."
82 | );
83 |
84 | $this->assertTrue($this->cache->save('data', 'id', null, 3600));
85 | $this->assertEquals(3600, $this->cache->getTtl('id'), null, 5);
86 | // $this->assertLessThanOrEqual(3600, $this->cache->getTtl('id'));
87 | }
88 |
89 | public function testTllFromLoad()
90 | {
91 | $this->assertFalse($this->cache->getTtl('non-existant'));
92 |
93 | $this->assertNull($this->cache->load('id'));
94 | $this->assertEquals(0, $this->cache->getTtl('id'),
95 | "Expiration should be set to 0 (for ever) by default."
96 | );
97 |
98 | $this->assertTrue($this->cache->save('data', 'id', null, 3600));
99 | $this->assertEquals('data', $this->cache->load('id'));
100 | $this->assertEquals(3600, $this->cache->getTtl('id'), null, 5);
101 | // $this->assertLessThanOrEqual(3600, $this->cache->getTtl('id'));
102 | }
103 |
104 | ////
105 | // The tests belowe are tags related
106 | ////
107 |
108 | public function testSaveWithTagDisabled()
109 | {
110 | $this->cache->setOptions(array('tag_enable' => false));
111 |
112 | $this->assertTrue(
113 | $this->cache->save('data', 'id', array('tag1', 'tag2'))
114 | );
115 | $this->assertNull($this->cache->loadTag('tag1'));
116 | }
117 |
118 | /**
119 | * @group testme
120 | */
121 | public function testSaveWithJustOneSingularTag()
122 | {
123 | $this->assertTrue($this->cache->save('data', 'id', array('tag')));
124 | $ids = array($this->cache->mapKey('id'));
125 |
126 | $this->assertEquals($ids, $this->cache->loadTag('tag'));
127 | $this->assertEquals($ids, $this->cache->load('tag', 'tag'));
128 | }
129 |
130 | public function testSaveManyTags()
131 | {
132 | $this->assertTrue(
133 | $this->cache->save('data', 'id', array('tag1', 'tag2'))
134 | );
135 | $ids = array($this->cache->mapKey('id'));
136 |
137 | $this->assertEquals($ids, $this->cache->loadTag('tag2'));
138 | $this->assertEquals($ids, $this->cache->load('tag2', 'tag'));
139 | }
140 |
141 | public function testSaveWithOverlappingTags()
142 | {
143 | $this->assertTrue(
144 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
145 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
146 | );
147 |
148 | $ids = $this->cache->loadTag('tag2');
149 | $this->assertTrue(count($ids) == 2);
150 | $this->assertContains($this->cache->mapKey('id1'), $ids);
151 | $this->assertContains($this->cache->mapKey('id2'), $ids);
152 | }
153 |
154 | public function testClean()
155 | {
156 | $this->assertTrue(
157 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
158 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3', 'tag4'))
159 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
160 | );
161 |
162 | $this->assertTrue($this->cache->clean(array('tag4')));
163 | $this->assertFalse($this->cache->clean(array('tag4')));
164 | $this->assertFalse($this->cache->clean(array('non-existant')));
165 |
166 | $this->assertNull($this->cache->loadKey('id2'));
167 | $this->assertNull($this->cache->loadKey('id3'));
168 | $this->assertNull($this->cache->loadTag('tag4'));
169 | $this->assertEquals('data1', $this->cache->loadKey('id1'));
170 | }
171 |
172 | public function testDeleteAlsoRemoveTags()
173 | {
174 | $this->assertTrue(
175 | $this->cache->save('foo value', 'foo', array('foo_tag', 'all_tag'))
176 | && $this->cache->save('bar value', 'bar', array('bar_tag', 'all_tag'))
177 | );
178 |
179 | $this->assertContains(
180 | $this->cache->mapKey('foo'), $this->cache->loadTag('foo_tag')
181 | );
182 | $this->assertTrue($this->cache->delete('foo'));
183 | $this->assertNull($this->cache->loadKey('foo'));
184 |
185 | $this->assertNull($this->cache->loadTag('foo_tag'));
186 |
187 | $this->assertContains(
188 | $this->cache->mapKey('bar'), $this->cache->loadTag('all_tag')
189 | );
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/tests/Indexer/ApcIndexerTest_OFF.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Indexer;
14 |
15 | use Apix\Cache,
16 | Apix\Cache\Indexer;
17 |
18 | class ApcIndexerTest extends GenericIndexerTestCase
19 | {
20 | protected $cache, $indexer;
21 |
22 | public function setUp()
23 | {
24 | $this->skipIfMissing('apc');
25 |
26 | if (!ini_get('apc.enable_cli')) {
27 | self::markTestSkipped(
28 | 'apc.enable_cli MUST be enabled in order to run this unit test'
29 | );
30 | }
31 |
32 | $this->cache = new Cache\Apc($this->options);
33 |
34 | $this->indexer = new Indexer\ApcIndexer($this->indexKey, $this->cache);
35 | }
36 |
37 | public function tearDown()
38 | {
39 | if (null !== $this->cache) {
40 | $this->cache->flush(true);
41 | unset($this->cache, $this->indexer);
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Indexer/GenericIndexerTestCase.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Indexer;
14 |
15 | use Apix\Cache\tests\TestCase;
16 |
17 | class GenericIndexerTestCase extends TestCase
18 | {
19 | protected $indexKey = 'indexKey';
20 |
21 | public function testAddOneElement()
22 | {
23 | $this->assertTrue( $this->indexer->add('foo') );
24 | $this->assertSame(array('foo'), $this->indexer->load());
25 | }
26 |
27 | public function testAddManyElements()
28 | {
29 | $this->assertTrue( $this->indexer->add('foo') );
30 | $this->assertTrue( $this->indexer->add(array('bar', 'baz')));
31 | $this->assertSame(array('foo', 'bar', 'baz'), $this->indexer->load());
32 | }
33 |
34 | public function testRemoveOneElement()
35 | {
36 | $this->assertNull($this->indexer->load());
37 |
38 | $this->assertTrue( $this->indexer->add(array('foo', 'bar')) );
39 | $this->assertSame(array('foo', 'bar'), $this->indexer->load());
40 |
41 | $this->assertTrue( $this->indexer->remove('bar') );
42 | $this->assertSame(array('foo'), $this->indexer->load());
43 | }
44 |
45 | public function testRemoveManyElements()
46 | {
47 | $items = array('foo', 'bar', 'baz');
48 | $this->assertNull($this->indexer->load());
49 |
50 | $this->assertTrue( $this->indexer->add($items) );
51 | $this->assertSame($items, $this->indexer->load());
52 |
53 | $this->assertTrue( $this->indexer->Remove(array('foo', 'bar')) );
54 | $this->assertSame(array('baz'), $this->indexer->load());
55 | }
56 |
57 | public function testGetName()
58 | {
59 | $this->assertSame($this->indexKey, $this->indexer->getName());
60 | }
61 |
62 | public function testGetItems()
63 | {
64 | $this->assertSame(array(), $this->indexer->getItems());
65 |
66 | // $items = array('foo', 'bar', 'baz');
67 | // $this->assertTrue( $this->indexer->add($items) );
68 | // $this->assertSame($items, $this->indexer->getItems());
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Indexer/MemcachedIndexerTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Indexer;
14 |
15 | use Apix\Cache,
16 | Apix\Cache\Indexer;
17 |
18 | class MemcachedIndexerTest extends GenericIndexerTestCase
19 | {
20 | const HOST = '127.0.0.1';
21 | const PORT = 11211;
22 | const AUTH = null;
23 |
24 | protected $cache, $memcached, $indexer;
25 |
26 | public function getMemcached()
27 | {
28 | try {
29 | $m = new \Memcached();
30 | $m->addServer(self::HOST, self::PORT);
31 |
32 | $stats = $m->getStats();
33 | $host = self::HOST.':'.self::PORT;
34 | if($stats[$host]['pid'] == -1)
35 | throw new \Exception(
36 | sprintf('Unable to reach a memcached server on %s', $host)
37 | );
38 |
39 | } catch (\Exception $e) {
40 | $this->markTestSkipped( $e->getMessage() );
41 | }
42 |
43 | return $m;
44 | }
45 |
46 | public function setUp()
47 | {
48 | $this->skipIfMissing('memcached');
49 | $this->memcached = $this->getMemcached();
50 | $this->cache = new Cache\Memcached($this->memcached, $this->options);
51 |
52 | $this->indexer = new Indexer\MemcachedIndexer($this->indexKey, $this->cache);
53 | }
54 |
55 | public function tearDown()
56 | {
57 | if (null !== $this->cache) {
58 | $this->cache->flush(true);
59 | $this->memcached->quit();
60 | unset($this->cache, $this->memcached, $this->indexer);
61 | }
62 | }
63 |
64 | public function testLoadDoesPurge()
65 | {
66 | $keys = range(1, 101);
67 | $this->assertTrue($this->indexer->add('a'));
68 | $this->assertTrue($this->indexer->remove($keys));
69 |
70 | $keyStr = implode(' -', $keys);
71 | $this->assertEquals(
72 | 'a -' . $keyStr . ' ', $this->cache->get($this->indexKey)
73 | );
74 |
75 | $this->assertEquals(array('a'), $this->indexer->load() );
76 |
77 | $this->assertNull($this->cache->get($this->indexKey));
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/tests/MemcachedTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | class MemcachedTest extends GenericTestCase
18 | {
19 | const HOST = '127.0.0.1';
20 | const PORT = 11211;
21 | const AUTH = null;
22 |
23 | /** @var \Apix\Cache\AbstractCache */
24 | protected $cache;
25 |
26 | /** @var \Memcached */
27 | protected $memcached;
28 |
29 | protected $options = array(
30 | 'prefix_key' => 'key_',
31 | 'prefix_tag' => 'tag_',
32 | 'prefix_idx' => 'idx_',
33 | 'serializer' => 'php'
34 | );
35 |
36 | public function getMemcached()
37 | {
38 | try {
39 | $m = new \Memcached();
40 | $m->addServer(self::HOST, self::PORT);
41 |
42 | $stats = $m->getStats();
43 | $host = self::HOST.':'.self::PORT;
44 | if($stats[$host]['pid'] == -1)
45 | throw new \Exception(
46 | sprintf('Unable to reach a memcached server on %s', $host)
47 | );
48 |
49 | } catch (\Exception $e) {
50 | $this->markTestSkipped( $e->getMessage() );
51 | }
52 |
53 | return $m;
54 | }
55 |
56 | public function setUp()
57 | {
58 | $this->skipIfMissing('memcached');
59 | $this->memcached = $this->getMemcached();
60 | $this->cache = new Cache\Memcached($this->memcached, $this->options);
61 | }
62 |
63 | public function tearDown()
64 | {
65 | if (null !== $this->cache) {
66 | $this->cache->flush(true);
67 | $this->memcached->quit();
68 | unset($this->cache, $this->memcached);
69 | }
70 | }
71 |
72 | public function _commonMemcachedData()
73 | {
74 | return $this->assertTrue(
75 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
76 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3', 'tag4'))
77 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
78 | );
79 | $this->assertSame('data3', $this->cache->loadKey('id3'));
80 | }
81 |
82 | public function testSaveIsUniqueAndOverwrite()
83 | {
84 | $this->assertTrue(
85 | $this->cache->save('bar1', 'foo')
86 | && $this->cache->save('bar2', 'foo')
87 | );
88 | $this->assertEquals('bar2', $this->cache->loadKey('foo'));
89 | }
90 |
91 | public function testFlushNamespace()
92 | {
93 | $this->_commonMemcachedData();
94 |
95 | $otherMemcached = $this->getMemcached();
96 | $otherMemcached->add('foo', 'bar');
97 |
98 | $this->assertTrue($this->cache->flush(), "Flush the namespace");
99 |
100 | $this->assertEquals('bar', $otherMemcached->get('foo'));
101 |
102 | $this->assertNull($this->cache->loadKey('id3'));
103 | $this->assertNull($this->cache->loadTag('tag1'));
104 | }
105 |
106 | public function testFlushIncrementsTheNamspaceIndex()
107 | {
108 | $this->_commonMemcachedData();
109 | $ns = $this->cache->getOption('prefix_nsp');
110 |
111 | $this->assertEquals($ns.'1_', $this->cache->getNamespace());
112 | $this->assertTrue($this->cache->flush(), "Flush the namespace");
113 | $this->assertEquals($ns.'2_', $this->cache->getNamespace());
114 | }
115 |
116 | public function testFlushAll()
117 | {
118 | $this->_commonMemcachedData();
119 |
120 | $this->getMemcached()->add('foo', 'bar');
121 |
122 | $this->assertTrue($this->cache->flush(true));
123 | $this->assertNull($this->cache->get('foo'));
124 | $this->assertNull($this->cache->loadKey('id3'));
125 | $this->assertNull($this->cache->loadTag('tag1'));
126 | }
127 |
128 | public function testDeleteWithTagDisabled()
129 | {
130 | $this->cache->setOptions(array('tag_enable' => false));
131 |
132 | $this->assertTrue(
133 | $this->cache->save('data', 'id', array('tag1', 'tag2'))
134 | );
135 |
136 | $this->assertTrue($this->cache->delete('id'));
137 | $this->assertNull($this->cache->loadTag('tag1'));
138 |
139 | $idxKey = $this->cache->mapIdx('id');
140 | $this->assertNull($this->cache->getIndex($idxKey)->load());
141 | }
142 |
143 | /**
144 | * @group encours
145 | */
146 | public function testDelete()
147 | {
148 | $tags = array('tag1', 'tag2');
149 | $this->assertTrue($this->cache->save('data', 'id', $tags));
150 |
151 | $this->assertSame(
152 | array($this->cache->mapKey('id')), $this->cache->loadTag('tag1'),
153 | 'tag1 isset'
154 | );
155 |
156 | // check the idx isset
157 | $indexer = $this->cache->getIndex($this->cache->mapIdx('id'));
158 | $this->assertSame( $tags, $indexer->load(), 'idx_id isset');
159 |
160 | $this->assertTrue($this->cache->delete('id'));
161 | $this->assertFalse($this->cache->delete('id'));
162 |
163 | $this->assertNull($this->cache->loadTag('tag1'), 'tag1 !isset');
164 | $this->assertNull($indexer->load(), 'idx_id !isset');
165 | }
166 |
167 | public function testIndexing()
168 | {
169 | $this->assertTrue(
170 | $this->cache->save('data1', 'id1', array('tag1', 'tag2', 'tag3'))
171 | );
172 | $idx = $this->cache->mapIdx('id1');
173 | $this->assertEquals(
174 | 'tag1 tag2 tag3 ', $this->cache->get($idx)
175 | );
176 | $this->assertTrue(
177 | $this->cache->getIndex($idx)->remove(array('tag3'))
178 | // $this->cache->saveIndex($idx, array('tag3'), '-')
179 | );
180 | $this->assertEquals(
181 | 'tag1 tag2 tag3 -tag3 ', $this->cache->get($idx)
182 | );
183 | }
184 |
185 | public function OFF_testShortTtlDoesExpunge()
186 | {
187 | $this->assertTrue(
188 | $this->cache->save('ttl-1', 'ttlId', array('someTags!'), -1)
189 | );
190 |
191 | // How to forcibly run garbage collection?
192 | // $this->cache->db->command(array(
193 | // 'reIndex' => 'cache'
194 | // ));
195 |
196 | $this->assertNull( $this->cache->load('ttlId') );
197 | }
198 |
199 | public function testSetSerializerToNull()
200 | {
201 | $this->cache->setSerializer(null);
202 | $this->assertSame(
203 | \Memcached::SERIALIZER_PHP, $this->cache->getSerializer()
204 | );
205 | }
206 |
207 | public function testSetSerializerToPhp()
208 | {
209 | $this->cache->setSerializer('php');
210 | $this->assertSame(
211 | \Memcached::SERIALIZER_PHP, $this->cache->getSerializer()
212 | );
213 | }
214 |
215 | public function testSetSerializerToJson()
216 | {
217 | if (defined('\Memcached::SERIALIZER_JSON')
218 | && \Memcached::HAVE_JSON
219 | ) {
220 | $this->cache->setSerializer('json');
221 | $this->assertSame(
222 | \Memcached::SERIALIZER_JSON, $this->cache->getSerializer()
223 | );
224 | }
225 | }
226 |
227 | public function testSetSerializerToJsonArray()
228 | {
229 | if (defined('\Memcached::SERIALIZER_JSON_ARRAY')
230 | && \Memcached::HAVE_JSON
231 | ) {
232 | $this->cache->setSerializer('json_array');
233 | $this->assertSame(
234 | \Memcached::SERIALIZER_JSON_ARRAY, $this->cache->getSerializer()
235 | );
236 | }
237 | }
238 |
239 | public function testSetSerializerToIgbinary()
240 | {
241 | if (defined('\Memcached::SERIALIZER_IGBINARY')
242 | && \Memcached::HAVE_IGBINARY
243 | ) {
244 | $this->cache->setSerializer('igBinary');
245 | $this->assertSame(
246 | \Memcached::SERIALIZER_IGBINARY, $this->cache->getSerializer()
247 | );
248 | }
249 | }
250 |
251 | public function testSetSerializerToMsgpack()
252 | {
253 | if (defined('\Memcached::SERIALIZER_MSGPACK')
254 | && \Memcached::HAVE_MSGPACK
255 | ) {
256 | $this->cache->setSerializer('msgpack');
257 | $this->assertSame(
258 | \Memcached::SERIALIZER_MSGPACK, $this->cache->getSerializer()
259 | );
260 | }
261 | }
262 |
263 | public function testIncrement()
264 | {
265 | $this->options = array('tag_enable' => true);
266 |
267 | $this->cache = new Cache\Memcached($this->memcached, $this->options);
268 |
269 | $this->assertNull($this->cache->get('testInc'));
270 | $this->assertEquals(1, $this->cache->increment('testInc'));
271 | $this->assertEquals(1, $this->cache->get('testInc'));
272 | $this->assertEquals(2, $this->cache->increment('testInc'));
273 | $this->assertEquals(2, $this->cache->get('testInc'));
274 | }
275 |
276 | // public function testIncrementWithBinaryProtocole()
277 | // {
278 | // $m = $this->getMemcached();
279 | // $m->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
280 |
281 | // $opts = array('tag_enable' => false);
282 | // $cache = new Memcached($m, $opts);
283 |
284 | // $this->assertEquals(1, $cache->increment('testInc'));
285 | // $this->assertEquals(1, $cache->get('testInc'));
286 |
287 | // $this->assertEquals(2, $cache->increment('testInc'));
288 | // $this->assertEquals(2, $cache->get('testInc'));
289 | // }
290 |
291 | }
292 |
--------------------------------------------------------------------------------
/tests/MongoTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | /**
18 | * MongoTest
19 | *
20 | * @package Apix\Cache
21 | * @author Franck Cassedanne
22 | */
23 | class MongoTest extends GenericTestCase
24 | {
25 | protected $cache, $mongo;
26 |
27 | public function setUp()
28 | {
29 | if (phpversion() >= '7.0.0' || defined('HHVM_VERSION')) {
30 | $this->skipIfMissing('mongodb');
31 | $class = '\MongoDB\Client';
32 | } else {
33 | $this->skipIfMissing('mongo');
34 | $class = '\MongoClient';
35 | }
36 |
37 | try {
38 | $this->mongo = new $class();
39 | } catch (\Exception $e) {
40 | $this->markTestSkipped( $e->getMessage() );
41 | }
42 |
43 | $this->cache = new Cache\Mongo($this->mongo, $this->options);
44 | }
45 |
46 | public function tearDown()
47 | {
48 | if (null !== $this->cache) {
49 | $this->cache->flush();
50 | if (method_exists($this->mongo, 'close'))
51 | $this->mongo->close();
52 | unset($this->cache, $this->mongo);
53 | }
54 | }
55 |
56 | public function testSaveIsUnique()
57 | {
58 | $this->assertTrue($this->cache->save('bar1', 'foo'));
59 | $this->assertTrue($this->cache->save('bar2', 'foo'));
60 |
61 | $this->assertEquals('bar2', $this->cache->loadKey('foo'));
62 |
63 | $this->assertEquals(1, $this->cache->count('foo') );
64 | }
65 |
66 | public function testFlushCacheOnly()
67 | {
68 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'));
69 | $this->cache->save('data2', 'id2', array('tag2', 'tag3'));
70 | $this->cache->save('data3', 'id3', array('tag3', 'tag4'));
71 |
72 | $foo = array('foo' => 'bar');
73 | $this->cache->collection->insert($foo);
74 |
75 | $this->assertTrue($this->cache->flush());
76 |
77 | $check = $this->cache->collection->findOne(array('foo'=>'bar'));
78 | if (is_a($check, 'ArrayObject')) $check = $check->getArrayCopy();
79 | $this->assertEquals($foo, $check);
80 |
81 | $this->assertNull($this->cache->loadKey('id3'));
82 | $this->assertNull($this->cache->loadTag('tag1'));
83 | }
84 |
85 | public function testFlushAll()
86 | {
87 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'));
88 | $this->cache->save('data2', 'id2', array('tag2', 'tag3'));
89 | $this->cache->save('data3', 'id3', array('tag3', 'tag4'));
90 |
91 | $this->cache->collection->insert(array('key' => 'foobar'));
92 |
93 | $this->assertTrue($this->cache->flush(true));
94 | $this->assertNull($this->cache->collection->findOne(array('key')));
95 |
96 | $this->assertNull($this->cache->loadKey('id3'));
97 | $this->assertNull($this->cache->loadTag('tag1'));
98 | }
99 |
100 | public function testShortTtlDoesExpunge()
101 | {
102 | $this->assertTrue(
103 | $this->cache->save('ttl-1', 'ttlId', array('someTags!'), -1)
104 | );
105 |
106 | // How to forcibly run garbage collection?
107 | // $this->cache->db->command(array(
108 | // 'reIndex' => 'cache'
109 | // ));
110 |
111 | $this->assertNull( $this->cache->loadKey('ttlId') );
112 | }
113 |
114 | public function testArrayWithArbitraryKeys()
115 | {
116 | $arr = array('A:B' => 'test', 'A&B' => 'test2', 'A.B' => 'test3');
117 |
118 | $this->assertTrue($this->cache->save($arr, 'array', array(), 5));
119 | $this->assertEquals($arr, $this->cache->loadKey('array'));
120 | }
121 |
122 | /**
123 | * @expectedException InvalidArgumentException
124 | */
125 | public function testThrowAnInvalidArgumentException()
126 | {
127 | new Cache\Mongo(new \stdClass);
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/tests/PdoTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 | use Apix\Cache\AbstractPdo;
17 |
18 | /**
19 | * @covers Apix\Cache\AbstractPdo
20 | * @covers Apix\Cache\Pdo\Sqlite
21 | * @covers Apix\Cache\Pdo\Mysql
22 | * @covers Apix\Cache\Pdo\Pgsql
23 | * @covers Apix\Cache\Pdo\Sql1999
24 | */
25 | class PdoTest extends GenericTestCase
26 | {
27 | /** @var AbstractPdo */
28 | protected $cache;
29 | protected $pdo;
30 |
31 | protected $options = array(
32 | 'db_name' => 'apix_tests',
33 | 'db_table' => 'cache'
34 | );
35 |
36 | public function pdoProvider()
37 | {
38 | $dbs = array(
39 | 'sqlite' => array(
40 | 'pdo_sqlite',
41 | function () {
42 | return new \PDO('sqlite::memory:');
43 | },
44 | 'Sqlite'
45 | ),
46 | 'mysql' => array(
47 | 'pdo_mysql',
48 | function () {
49 | return new \PDO(
50 | 'mysql:dbname=apix_tests;host=127.0.0.1', 'root'
51 | );
52 | },
53 | 'Mysql'
54 | ),
55 | 'pgsql' => array(
56 | 'pdo_pgsql',
57 | function () {
58 | return new \PDO(
59 | 'pgsql:dbname=apix_tests;host=127.0.0.1', 'postgres'
60 | );
61 | },
62 | 'Pgsql'
63 | ),
64 | 'sql1999' => array(
65 | 'pdo_sqlite',
66 | function () {
67 | return new \PDO('sqlite::memory:');
68 | },
69 | 'Sql1999'
70 | )
71 | );
72 | $DB = getenv('DB') ? getenv('DB') : 'sqlite';
73 |
74 | if (in_array($DB, array_keys($dbs))) {
75 | return $dbs[$DB];
76 | }
77 |
78 | $this->markTestSkipped("Unsupported PDO DB ($DB) environment.");
79 | }
80 |
81 | public function setUp()
82 | {
83 | list($ext_name, $dbh, $class) = $this->pdoProvider();
84 |
85 | $this->skipIfMissing('pdo');
86 | $this->skipIfMissing($ext_name);
87 |
88 | try {
89 | $this->pdo = $dbh();
90 | } catch (\Exception $e) {
91 | $this->markTestSkipped( $e->getMessage() );
92 | }
93 |
94 | $this->classname = 'Apix\\Cache\\Pdo\\' . $class;
95 | $this->cache = new $this->classname($this->pdo, $this->options);
96 | }
97 |
98 | public function tearDown()
99 | {
100 | if (null !== $this->cache) {
101 | $this->cache->flush(true);
102 | unset($this->cache, $this->pdo, $this->classname);
103 | }
104 | }
105 |
106 | public function testSaveIsUnique()
107 | {
108 | $this->assertTrue($this->cache->save('bar_1', 'foo'));
109 | $this->assertEquals('bar_1', $this->cache->load('foo'));
110 |
111 | $this->assertTrue($this->cache->save('bar_2', 'foo'));
112 | $this->assertEquals('bar_2', $this->cache->load('foo'));
113 |
114 | // $this->assertEquals(1, $this->cache->getAdapter()->rowCount() );
115 | }
116 |
117 | public function testFlushCacheOnly()
118 | {
119 | $this->assertTrue(
120 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
121 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
122 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
123 | );
124 | // $foo = array('foo' => 'bar');
125 | // $this->cache->getAdapter()->add($foo);
126 |
127 | $this->assertTrue($this->cache->flush());
128 |
129 | // $this->assertEquals(
130 | // $foo,
131 | // $this->cache->getAdapter()->findOne(array('foo'=>'bar'))
132 | // );
133 |
134 | $this->assertNull($this->cache->load('id3'));
135 | $this->assertNull($this->cache->load('tag1', 'tag'));
136 | }
137 |
138 | /**
139 | * With multiple caches in play, calling flush() should only remove the
140 | * entries for the cache instance it is called on.
141 | */
142 | public function testFlushCacheOnly_secondCache()
143 | {
144 | $options = array(
145 | 'prefix_key' => 'second-cache-key:', // prefix cache keys
146 | 'prefix_tag' => 'second-cache-tag:', // prefix cache tags
147 | 'tag_enable' => true // wether to enable tagging
148 | );
149 |
150 | /** @var AbstractPdo */
151 | $localCache = new $this->classname($this->pdo, $options);
152 |
153 | $this->assertTrue(
154 | $this->cache->save('data1_1', 'id1_1', array('tag1_1', 'tag1_2')) &&
155 | $this->cache->save('data1_2', 'id1_2', array('tag1_2', 'tag1_3')) &&
156 | $this->cache->save('data1_3', 'id1_3', array('tag1_3', 'tag1_4'))
157 | );
158 |
159 | $this->assertTrue(
160 | $localCache->save('data2_1', 'id2_1', array('tag2_1', 'tag2_2')) &&
161 | $localCache->save('data2_2', 'id2_2', array('tag2_2', 'tag2_3')) &&
162 | $localCache->save('data2_3', 'id2_3', array('tag2_3', 'tag2_4'))
163 | );
164 |
165 | $result = $this->cache->flush();
166 | $this->assertTrue($result);
167 |
168 | $this->assertNull($this->cache->load('id1_3'));
169 | $this->assertNull($this->cache->load('tag1_1', 'tag'));
170 |
171 | $this->assertNotNull($localCache->load('id2_3'));
172 | $this->assertNotNull($localCache->load('tag2_1', 'tag'));
173 |
174 | unset($localCache);
175 | }
176 |
177 | /**
178 | *
179 | */
180 | public function testFlushAll()
181 | {
182 | $this->assertTrue(
183 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
184 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
185 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
186 | );
187 |
188 | $this->assertTrue($this->cache->flush(true));
189 |
190 | try {
191 | /*
192 | * Previously flush() would have dropped the cache table which
193 | * would result in a PDOException being thrown if the table were
194 | * subsequently be accessed.
195 | */
196 | $this->assertNull($this->cache->load('id3'));
197 | $this->assertNull($this->cache->load('tag1', 'tag'));
198 | }
199 | catch (\PDOException $e) {
200 | /* Because the cache table is no longer dropped we should never
201 | * reach this point.
202 | */
203 | $this->fail('PDOException should not have been thrown');
204 | }
205 | }
206 |
207 | /**
208 | * With multiple caches in play, calling flush(true) should remove all of
209 | * the entries from the database regardless of which cache instance they
210 | * are associated with.
211 | */
212 | public function testFlushAll_secondCache()
213 | {
214 | $options = array(
215 | 'prefix_key' => 'second-cache-key:', // prefix cache keys
216 | 'prefix_tag' => 'second-cache-tag:', // prefix cache tags
217 | 'tag_enable' => true // wether to enable tagging
218 | );
219 |
220 | /** @var AbstractPdo */
221 | $localCache = new $this->classname($this->pdo, $options);
222 |
223 | $this->assertTrue(
224 | $this->cache->save('data1_1', 'id1_1', array('tag1_1', 'tag1_2')) &&
225 | $this->cache->save('data1_2', 'id1_2', array('tag1_2', 'tag1_3')) &&
226 | $this->cache->save('data1_3', 'id1_3', array('tag1_3', 'tag1_4'))
227 | );
228 |
229 | $this->assertTrue(
230 | $localCache->save('data2_1', 'id2_1', array('tag2_1', 'tag2_2')) &&
231 | $localCache->save('data2_2', 'id2_2', array('tag2_2', 'tag2_3')) &&
232 | $localCache->save('data2_3', 'id2_3', array('tag2_3', 'tag2_4'))
233 | );
234 |
235 | $this->assertTrue($this->cache->flush(true));
236 |
237 | $this->assertNull($this->cache->load('id1_3'));
238 | $this->assertNull($this->cache->load('tag1_1', 'tag'));
239 |
240 | $this->assertNull($localCache->load('id2_3'));
241 | $this->assertNull($localCache->load('tag2_1', 'tag'));
242 |
243 | unset($localCache);
244 | }
245 |
246 | public function testShortTtlDoesExpunge()
247 | {
248 | $this->assertTrue(
249 | $this->cache->save('ttl-1', 'ttlId', array('someTags!'), -1)
250 | );
251 |
252 | $this->assertNull( $this->cache->load('ttlId') );
253 | }
254 |
255 | public function testTtlSetToNull()
256 | {
257 | $this->assertTrue(
258 | $this->cache->save('ttl-null', 'ttlId', array('someTags!'), null)
259 | );
260 |
261 | $this->assertEquals('ttl-null', $this->cache->load('ttlId') );
262 | }
263 |
264 | public function testPurge()
265 | {
266 | $this->assertTrue(
267 | $this->cache->save('120s', 'id1', null, 120)
268 | && $this->cache->save('600s', 'id2', null, 600)
269 | );
270 | $this->assertEquals('120s', $this->cache->load('id1'));
271 | $this->assertTrue($this->cache->purge(130));
272 | $this->assertFalse($this->cache->purge());
273 | $this->assertNull($this->cache->load('id1'));
274 |
275 | $this->assertEquals('600s', $this->cache->load('id2'));
276 | $this->assertTrue($this->cache->purge(630));
277 | $this->assertNull($this->cache->load('id2'));
278 | }
279 |
280 | public function testcreateIndexTableReturnsFalse()
281 | {
282 | $this->assertFalse( $this->cache->createIndexTable('not-defined') );
283 | }
284 |
285 | public function testGetDriverName()
286 | {
287 | if ($this->classname != 'Apix\Cache\Pdo\Sql1999') {
288 | $this->assertSame(
289 | $this->classname,
290 | 'Apix\\Cache\\Pdo\\'
291 | . Cache\AbstractPdo::getDriverName($this->pdo)
292 | );
293 | }
294 | }
295 |
296 | /**
297 | * Regression test for pull request GH#28
298 | *
299 | * @link https://github.com/frqnck/apix-cache/pull/28
300 | * PDOException: SQLSTATE[23000]: Integrity constraint violation:
301 | * 1062 Duplicate entry 'apix-cache-key:same_key' for key 'PRIMARY'
302 | * @group pr
303 | */
304 | public function testPullRequest28()
305 | {
306 | $this->cache->save('same_data', 'same_key');
307 | $this->cache->save('same_data', 'same_key');
308 |
309 | $this->assertEquals('same_data', $this->cache->load('same_key'));
310 | }
311 |
312 | /**
313 | * @requires PHP 7.3
314 | */
315 | public function testCacheMiss_loadKey()
316 | {
317 | $cacheMissKey = 'CacheMissKey';
318 |
319 | try {
320 | $result = $this->cache->loadKey($cacheMissKey);
321 |
322 | $this->assertNull($result);
323 | } catch (\Exception $e) {
324 | $this->fail('Exception should not have been thrown');
325 | }
326 | }
327 |
328 | /**
329 | * @requires PHP 7.3
330 | */
331 | public function testCacheMiss_loadTag()
332 | {
333 | $cacheMissTag = 'CacheMissTag';
334 |
335 | try {
336 | $result = $this->cache->loadTag($cacheMissTag);
337 |
338 | $this->assertNull($result);
339 | } catch (\Exception $e) {
340 | $this->fail('Exception should not have been thrown');
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/tests/PsrCache/ItemTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\PsrCache;
14 |
15 | use Apix\Cache\tests\TestCase;
16 | use Apix\Cache\PsrCache\Item;
17 |
18 | class ItemTest extends TestCase
19 | {
20 | protected $item = null;
21 |
22 | public function setUp()
23 | {
24 | $this->item = new Item('foo');
25 | }
26 |
27 | public function tearDown()
28 | {
29 | if (null !== $this->item) {
30 | unset($this->item);
31 | }
32 | }
33 |
34 | public function testItemConstructor()
35 | {
36 | $item = new Item('foo', 'bar');
37 | $this->assertEquals('foo', $item->getKey());
38 | $this->assertNull($item->get());
39 |
40 | $this->assertFalse($item->isHit());
41 | }
42 |
43 | public function testItemConstructorHittingCache()
44 | {
45 | $item = new Item('foo', 'bar', null, true);
46 | $this->assertEquals('foo', $item->getKey());
47 | $this->assertEquals('bar', $item->get());
48 |
49 | $this->assertTrue($item->isHit());
50 | }
51 |
52 | public function testSetAndisHit()
53 | {
54 | $this->assertSame($this->item, $this->item->set('new foo value'));
55 | $this->assertFalse($this->item->isHit());
56 |
57 | $this->assertSame($this->item, $this->item->setHit(true));
58 | $this->assertSame('new foo value', $this->item->get());
59 | }
60 |
61 | public function expiresAtProvider()
62 | {
63 | return array(
64 | 'DateTime' => array(new \DateTime('1 day'), '1 day', 86400),
65 | 'Zero to Now' => array(0, null, 0), // or 'now' (same)
66 | 'Integer' => array(999, '+999 seconds', 999),
67 | 'Null' => array(null, Item::DEFAULT_EXPIRATION, null),
68 | );
69 | }
70 |
71 | /**
72 | * @dataProvider expiresAtProvider
73 | */
74 | public function testExpiresAt($from, $to)
75 | {
76 | $this->assertSame($this->item, $this->item->expiresAt($from));
77 | $date = new \DateTime($to);
78 |
79 | $expire = $this->item->getExpiration();
80 | $this->assertInstanceOf('DateTime', $expire);
81 |
82 | $this->assertEquals(
83 | (int) $date->format('U'), $expire->format('U'), '', 10
84 | );
85 | }
86 |
87 | /**
88 | * @dataProvider expiresAtProvider
89 | */
90 | public function testGetTtlInSecond($from, $to, $sec)
91 | {
92 | if ($sec !== null) {
93 | $this->item->expiresAt($from);
94 | $this->assertEquals($sec, $this->item->getTtlInSecond(), '', 10);
95 | }
96 | }
97 |
98 | public function expiresAfterProvider()
99 | {
100 | return array(
101 | 'DateInterval' => array(new \DateInterval('P1Y'), 'now+1year'),
102 | 'Zero to Now' => array(0, null), // or 'now' (same)
103 | 'Integer' => array(999, '+999 seconds'),
104 | 'Null' => array(null, Item::DEFAULT_EXPIRATION),
105 | );
106 | }
107 |
108 | /**
109 | * @dataProvider expiresAfterProvider
110 | */
111 | public function testExpiresAfter($from, $to)
112 | {
113 | $this->assertSame($this->item, $this->item->expiresAfter($from));
114 | $date = new \DateTime($to);
115 |
116 | $expire = $this->item->getExpiration();
117 | $this->assertInstanceOf('DateTime', $expire);
118 |
119 | $this->assertEquals(
120 | (int) $date->format('U'), $expire->format('U'), '', 10
121 | );
122 | }
123 |
124 | /**
125 | * @expectedException Apix\Cache\PsrCache\InvalidArgumentException
126 | */
127 | public function testExpiresAtThrowAnException()
128 | {
129 | $this->item->expiresAt('string');
130 | }
131 |
132 | /**
133 | * @expectedException Apix\Cache\PsrCache\InvalidArgumentException
134 | */
135 | public function testExpiresAfterThrowAnException()
136 | {
137 | $this->item->expiresAfter('string');
138 | }
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/tests/PsrCache/PoolTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\PsrCache;
14 |
15 | use Apix\Cache\tests\TestCase;
16 |
17 | use Apix\Cache,
18 | Apix\Cache\PsrCache\Pool;
19 |
20 | class PoolTest extends TestCase
21 | {
22 | protected $pool = null, $item = null;
23 |
24 | public function setUp()
25 | {
26 | $cache = new Cache\Runtime();
27 |
28 | $this->pool = new Pool($cache);
29 | $this->item = $this->pool->getItem('foo')->set('foo value');
30 | $this->assertTrue( $this->pool->save($this->item) );
31 | }
32 |
33 | public function tearDown()
34 | {
35 | unset($this->pool, $this->item);
36 | }
37 |
38 | public function testGetItemWithNonExistantKey()
39 | {
40 | $item = $this->pool->getItem('non-existant');
41 | $this->assertInstanceOf('Psr\Cache\CacheItemInterface', $item);
42 | }
43 |
44 | /**
45 | * The following characters are reserved for future extensions
46 | * and MUST NOT be supported by implementing libraries:
47 | * {}()/\@:
48 | *
49 | * @return array
50 | */
51 | public static function _invalidKeyProvider()
52 | {
53 | return array(
54 | array('foo{bar'),
55 | array('foo}bar'),
56 | array('foo(bar'),
57 | array('foo)bar'),
58 | array('foo/bar'),
59 | array('foo\\bar'),
60 | array('foo@bar'),
61 | array('foo:bar'),
62 | 'null' => array( null ),
63 | 'boolean' => array( true ),
64 | 'integer' => array( 1 ),
65 | 'float' => array( 1.1 )
66 | );
67 | }
68 |
69 | /**
70 | * @dataProvider _invalidKeyProvider
71 | * @expectedException \Apix\Cache\PsrCache\InvalidArgumentException
72 | */
73 | public function testGetItemThrowsException($key)
74 | {
75 | $this->pool->getItem($key);
76 | }
77 |
78 | public function testBasicSetAndGetOperations()
79 | {
80 | // Create the 'bar' item.
81 | $item = $this->pool->getItem('bar');
82 |
83 | // Set a the 'bar' item value.
84 | $item->set('bar value');
85 | $this->assertNull($item->get());
86 | $this->pool->save($item);
87 | $this->assertEquals('bar value', $item);
88 |
89 | // Update the 'bar' item value.
90 | $item->set('new bar value');
91 | $this->assertNull($item->get());
92 | $this->pool->save($item);
93 | $this->assertEquals('new bar value', $item);
94 | }
95 |
96 | public function testGetItems()
97 | {
98 | $this->assertSame(array(), $this->pool->getItems());
99 |
100 | $items = $this->pool->getItems(array('non-existant'));
101 | $this->assertInstanceOf(
102 | '\Psr\Cache\CacheItemInterface', $items['non-existant']
103 | );
104 |
105 | $items = $this->pool->getItems( array('foo') );
106 | $this->assertEquals('foo value', $items['foo']);
107 | }
108 |
109 | public function testSave()
110 | {
111 | $item = $this->pool->getItem('baz')->set('foo value');
112 | $this->assertFalse($item->isHit());
113 | $this->assertTrue($this->pool->save($item));
114 | }
115 |
116 | public function testClear()
117 | {
118 | $this->assertTrue($this->pool->clear());
119 |
120 | $item = $this->pool->getItem('foo');
121 |
122 | $this->assertFalse($item->isHit());
123 | }
124 |
125 | public function testDeleteItems()
126 | {
127 | $this->assertTrue(
128 | $this->pool->deleteItems(array('foo', 'non-existant'))
129 | );
130 |
131 | $this->assertFalse( $this->pool->hasItem('foo') );
132 | }
133 |
134 | public function testDeleteItem()
135 | {
136 | $this->assertTrue( $this->pool->deleteItem('foo') );
137 |
138 | $this->assertTrue( $this->pool->deleteItem('foo') );
139 | $this->assertFalse( $this->pool->hasItem('foo') );
140 | }
141 |
142 | /**
143 | * It MUST NOT be considered an error condition if the specified key does
144 | * not exist. The post-condition is the same (the key does not exist, or
145 | * the pool is empty), thus there is no error condition.
146 | */
147 | public function testDeleteNonExistant()
148 | {
149 | $this->assertTrue($this->pool->deleteItem('non-existant'));
150 | $this->assertFalse($this->pool->hasItem('non-existant'));
151 | }
152 |
153 | public function testSaveDeferredAndCommit()
154 | {
155 | $item = $this->pool->getItem('foo')->set('foo value');
156 | $this->assertSame($this->pool, $this->pool->saveDeferred($item));
157 |
158 | $this->assertNull($item->get());
159 |
160 | // get the deferred version
161 | $this->assertEquals('foo value', $this->pool->getItem('foo'));
162 |
163 | $this->assertTrue($this->pool->commit());
164 | $this->assertEquals('foo value', $item);
165 |
166 | $items = $this->pool->getItems(array('foo', 'bar'));
167 | $this->assertEquals('foo value', $items['foo']);
168 | }
169 |
170 | /**
171 | * Regression test for bug GH#13
172 | *
173 | * @link https://github.com/frqnck/apix-cache/issues/13
174 | * "TaggablePool and Pool overrides prefix_key and prefix_tag options
175 | * with hardcoded values"
176 | * @group regression
177 | */
178 | public function testBug13()
179 | {
180 | $pool = new Pool(
181 | new Cache\Runtime(array(), $this->options)
182 | );
183 | $adapter = $pool->getCacheAdapter();
184 |
185 | $this->assertSame(
186 | $this->options['prefix_key'],
187 | $adapter->getOption('prefix_key')
188 | );
189 | $this->assertSame(
190 | $this->options['prefix_tag'],
191 | $adapter->getOption('prefix_tag')
192 | );
193 | }
194 |
195 | public function testDestructDoesCommit()
196 | {
197 | $item = $this->pool->getItem('foo')->set('foo value');
198 | $this->assertSame($this->pool, $this->pool->saveDeferred($item));
199 | $this->pool->__destruct();
200 | $this->assertEquals('foo value', $item);
201 |
202 | $item = $this->pool->getItem('foo');
203 | $this->assertEquals('foo value', $item );
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/tests/PsrCache/TaggableItemTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\PsrCache;
14 |
15 | use Apix\Cache\PsrCache;
16 |
17 | class TaggableItemTest extends ItemTest
18 | {
19 | protected $item = null;
20 |
21 | public function setUp()
22 | {
23 | $this->item = new PsrCache\TaggableItem('foo');
24 | }
25 |
26 | public function tearDown()
27 | {
28 | unset($this->item);
29 | }
30 |
31 | public function testGetItemTagsIsNullByDefault()
32 | {
33 | $this->assertNull($this->item->getTags());
34 | }
35 |
36 | public function testSetItemTags()
37 | {
38 | $tags = array('fooTag', 'barTag');
39 | $this->assertSame($this->item, $this->item->setTags($tags));
40 | $this->assertSame($tags, $this->item->getTags());
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/tests/PsrCache/TaggablePoolTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\PsrCache;
14 |
15 | use Apix\Cache,
16 | Apix\Cache\PsrCache\TaggablePool;
17 |
18 | class TaggablePoolTest extends PoolTest
19 | {
20 | protected $cache = null, $pool = null, $item = null;
21 |
22 | public function setUp()
23 | {
24 | $this->cache = new Cache\Runtime();
25 |
26 | $this->pool = new TaggablePool($this->cache);
27 | $this->item = $this->pool->getItem('foo')->set('foo value');
28 | $this->pool->save($this->item);
29 |
30 | $this->assertTrue($this->item->isHit());
31 | }
32 |
33 | public function tearDown()
34 | {
35 | if (null !== $this->cache) {
36 | $this->cache->flush();
37 | unset($this->cache);
38 | }
39 | unset($this->pool, $this->item);
40 | }
41 |
42 | public function testGetItemsByTagIsEmptyArrayByDefault()
43 | {
44 | $this->assertEquals(
45 | array(),
46 | $this->pool->getItemsByTag('non-existant')
47 | );
48 | }
49 |
50 | public function testGetItemsByTag()
51 | {
52 | $tags = array('fooTag', 'barTag');
53 | $this->assertSame($this->item, $this->item->setTags($tags));
54 | $this->assertTrue($this->pool->save($this->item));
55 |
56 | $items = $this->pool->getItemsByTag('fooTag');
57 |
58 | $this->assertInstanceOf('Apix\Cache\PsrCache\TaggableItem', $items['foo']);
59 | $this->assertSame('foo', $items['foo']->getkey());
60 | $this->assertSame('foo value', $items['foo']->get());
61 |
62 | $this->assertSame($tags, $items['foo']->getTags());
63 | }
64 |
65 | public function testClearByTags()
66 | {
67 | $this->assertFalse($this->pool->clearByTags( array('non-existant') ));
68 |
69 | $tags = array('fooTag', 'barTag');
70 | $this->assertSame($this->item, $this->item->setTags($tags));
71 | $this->assertTrue($this->pool->save($this->item));
72 |
73 | $this->assertTrue($this->pool->clearByTags( array('fooTag') ));
74 | }
75 |
76 | /**
77 | * Regression test for bug GH#13
78 | *
79 | * @link https://github.com/frqnck/apix-cache/issues/13
80 | * "TaggablePool and Pool overrides prefix_key and prefix_tag options
81 | * with hardcoded values"
82 | * @group regression
83 | */
84 | public function testBug13()
85 | {
86 | $pool = new TaggablePool(
87 | new Cache\Runtime(array(), $this->options)
88 | );
89 | $adapter = $pool->getCacheAdapter();
90 |
91 | $this->assertSame(
92 | $this->options['prefix_key'],
93 | $adapter->getOption('prefix_key')
94 | );
95 | $this->assertSame(
96 | $this->options['prefix_tag'],
97 | $adapter->getOption('prefix_tag')
98 | );
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/tests/RedisTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | /**
18 | * RedisTest
19 | *
20 | * @package Apix\Cache
21 | * @author Franck Cassedanne
22 | */
23 | class RedisTest extends GenericTestCase
24 | {
25 | const HOST = '127.0.0.1';
26 | const PORT = 6379;
27 | const AUTH = null;
28 |
29 | protected $cache, $redis;
30 |
31 | public function setUp()
32 | {
33 | $this->skipIfMissing('redis');
34 |
35 | try {
36 | $this->redis = new \Redis();
37 | $this->redis->connect(self::HOST, self::PORT);
38 | if (self::AUTH) {
39 | $this->redis->auth(self::AUTH);
40 | }
41 | $this->redis->ping();
42 | } catch (\Exception $e) {
43 | $this->markTestSkipped( $e->getMessage() );
44 | }
45 |
46 | $this->cache = new Cache\Redis($this->redis, $this->options);
47 | }
48 |
49 | public function tearDown()
50 | {
51 | if (null !== $this->cache) {
52 | $this->cache->flush();
53 | $this->redis->close();
54 | unset($this->cache, $this->redis);
55 | }
56 | }
57 |
58 | public function testFlushSelected()
59 | {
60 | $this->assertTrue(
61 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
62 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
63 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
64 | );
65 |
66 | $this->redis->set('foo', 'bar');
67 | $this->assertTrue($this->cache->flush());
68 | $this->assertFalse($this->cache->flush());
69 | $this->assertTrue((boolean) $this->redis->exists('foo'));
70 |
71 | $this->assertNull($this->cache->loadKey('id3'));
72 | $this->assertNull($this->cache->loadTag('tag1'));
73 | }
74 |
75 | public function testFlushAll()
76 | {
77 | $this->assertTrue(
78 | $this->cache->save('data1', 'id1', array('tag1', 'tag2'))
79 | && $this->cache->save('data2', 'id2', array('tag2', 'tag3'))
80 | && $this->cache->save('data3', 'id3', array('tag3', 'tag4'))
81 | );
82 |
83 | $this->redis->set('foo', 'bar');
84 | $this->assertTrue($this->cache->flush(true)); // always true!
85 | $this->assertFalse((boolean) $this->redis->exists('foo'));
86 |
87 | $this->assertNull($this->cache->loadKey('id3'));
88 | $this->assertNull($this->cache->loadTag('tag1'));
89 | }
90 |
91 | public function testShortTtlDoesExpunge()
92 | {
93 | $this->cache->save('ttl-1', 'ttlId', null, -1);
94 | $this->assertNull( $this->cache->load('ttlId'));
95 | }
96 |
97 | public function testSetSerializerToPhp()
98 | {
99 | $this->cache->setSerializer('php');
100 | $this->assertSame(
101 | \Redis::SERIALIZER_PHP, $this->cache->getSerializer()
102 | );
103 | }
104 |
105 | public function testSetSerializerToIgBinary()
106 | {
107 | if (defined('Redis::SERIALIZER_IGBINARY')) {
108 | $this->cache->setSerializer('igBinary');
109 | $this->assertSame(
110 | \Redis::SERIALIZER_IGBINARY, $this->cache->getSerializer()
111 | );
112 | }
113 | }
114 |
115 | // msgpack 2.0.1, compatible with PHP 7
116 | public function testSetSerializerToMsgpack()
117 | {
118 | if (defined('Redis::SERIALIZER_MSGPACK')) {
119 | $this->cache->setSerializer('msgpack');
120 | $this->assertSame(
121 | \Redis::SERIALIZER_MSGPACK, $this->cache->getSerializer()
122 | );
123 | }
124 | }
125 |
126 | public function testSetSerializerToNull()
127 | {
128 | $this->cache->setSerializer(null);
129 | $this->assertSame(
130 | \Redis::SERIALIZER_NONE, $this->cache->getSerializer()
131 | );
132 | }
133 |
134 | public function testSetSerializerToJson()
135 | {
136 | $this->cache->setSerializer('json');
137 | $this->assertInstanceOf(
138 | 'Apix\Cache\Serializer\Json', $this->cache->getSerializer()
139 | );
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/tests/RuntimeTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | use Apix\Cache;
16 |
17 | /**
18 | * RuntimeTest
19 | *
20 | * @package Apix\Cache
21 | * @author Franck Cassedanne
22 | */
23 | class RuntimeTest extends GenericTestCase
24 | {
25 | protected $cache = null;
26 |
27 | public function setUp()
28 | {
29 | $this->cache = new Cache\Runtime(null, $this->options);
30 | }
31 |
32 | public function tearDown()
33 | {
34 | if (null !== $this->cache) {
35 | $this->cache->flush();
36 | unset($this->cache);
37 | }
38 | }
39 |
40 | public function testWithAPopulatedArray()
41 | {
42 | $options = array('prefix_key' => null, 'prefix_tag' => null);
43 | $pre_cached_items = array(
44 | 'foo' => array(
45 | 'data' => 'foo value', 'tags' => array('tag'), 'expire' => null
46 | )
47 | );
48 |
49 | $this->cache = new Cache\Runtime($pre_cached_items, $options);
50 | $this->assertSame('foo value', $this->cache->loadKey('foo'));
51 | $this->assertSame(array('foo'), $this->cache->loadTag('tag'));
52 | }
53 |
54 | // AbstratcCache
55 |
56 | public function testGetOption()
57 | {
58 | $this->assertSame($this->options['prefix_key'], $this->cache->getOption('prefix_key') );
59 | }
60 |
61 | public function testRemovePrefix()
62 | {
63 | $this->assertSame(
64 | '-str', $this->cache->removePrefix('prefix-str', 'prefix')
65 | );
66 | }
67 |
68 | public function testRemovePrefixKey()
69 | {
70 | $this->assertSame(
71 | 'foo',
72 | $this->cache->removePrefixKey($this->options['prefix_key'] . 'foo')
73 | );
74 | $this->assertSame(
75 | 'not-prefixed-key',
76 | $this->cache->removePrefixKey('not-prefixed-key')
77 | );
78 | }
79 | public function testRemovePrefixTag()
80 | {
81 | $this->assertSame(
82 | 'foo',
83 | $this->cache->removePrefixTag($this->options['prefix_tag'] . 'foo')
84 | );
85 | $this->assertSame(
86 | 'not-prefixed-key',
87 | $this->cache->removePrefixTag('not-prefixed-key')
88 | );
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/tests/Serializer/IgbinaryTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\Igbinary;
16 |
17 | class IgbinaryTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->skipIfMissing('igbinary');
23 | $this->serializer = new Igbinary;
24 |
25 | }
26 |
27 | /**
28 | * @dataProvider serializerProvider
29 | */
30 | public function testSerialize($var)
31 | {
32 | $this->assertEquals(
33 | \igbinary_serialize($var),
34 | $this->serializer->serialize($var)
35 | );
36 | }
37 |
38 | /**
39 | * @dataProvider serializerProvider
40 | */
41 | public function testUnserialize($var)
42 | {
43 | $this->assertEquals(
44 | $var,
45 | $this->serializer->unserialize(
46 | \igbinary_serialize($var)
47 | )
48 | );
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Serializer/JsonTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\Json;
16 |
17 | class JsonTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->skipIfMissing('json');
23 | $this->serializer = new Json;
24 | }
25 |
26 | /**
27 | * @dataProvider serializerProvider
28 | */
29 | public function testSerialize($var)
30 | {
31 | $this->assertEquals(
32 | \json_encode($var),
33 | $this->serializer->serialize($var)
34 | );
35 | }
36 |
37 | /**
38 | * @dataProvider serializerProvider
39 | */
40 | public function testUnserialize($var)
41 | {
42 | if(is_array($var)) $var = (object) $var;
43 |
44 | $this->assertEquals(
45 | $var,
46 | $this->serializer->unserialize(
47 | \json_encode($var)
48 | )
49 | );
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Serializer/MsgpackTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\Msgpack;
16 |
17 | class MsgpackTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->skipIfMissing('msgpack');
23 | $this->serializer = new Msgpack;
24 | }
25 |
26 | /**
27 | * @dataProvider serializerProvider
28 | */
29 | public function testSerialize($var)
30 | {
31 | $this->assertEquals(
32 | \msgpack_pack($var),
33 | $this->serializer->serialize($var)
34 | );
35 | }
36 |
37 | /**
38 | * @dataProvider serializerProvider
39 | */
40 | public function testUnserialize($var)
41 | {
42 | $this->assertEquals(
43 | $var,
44 | $this->serializer->unserialize(
45 | \msgpack_pack($var)
46 | )
47 | );
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Serializer/NoneTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\None;
16 |
17 | class NoneTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->serializer = new None;
23 | }
24 |
25 | /**
26 | * @dataProvider serializerProvider
27 | */
28 | public function testSerialize($var)
29 | {
30 | $this->assertEquals(
31 | $var,
32 | $this->serializer->serialize($var)
33 | );
34 | }
35 |
36 | /**
37 | * @dataProvider serializerProvider
38 | */
39 | public function testUnserialize($var)
40 | {
41 | $this->assertEquals(
42 | $var,
43 | $this->serializer->unserialize($var)
44 | );
45 | }
46 |
47 | /**
48 | * @dataProvider serializerProvider
49 | * @deprecated
50 | */
51 | public function testIsSerialized($var, $str = null)
52 | {
53 | $this->assertFalse(
54 | $this->serializer->isSerialized($var)
55 | );
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Serializer/PhpTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\Php;
16 |
17 | class PhpTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->serializer = new Php;
23 | }
24 |
25 | /**
26 | * @dataProvider serializerProvider
27 | */
28 | public function testSerialize($var)
29 | {
30 | $this->assertEquals(
31 | \serialize($var),
32 | $this->serializer->serialize($var)
33 | );
34 | }
35 |
36 | /**
37 | * @dataProvider serializerProvider
38 | */
39 | public function testUnserialize($var)
40 | {
41 | $this->assertEquals(
42 | $var,
43 | $this->serializer->unserialize(
44 | \serialize($var)
45 | )
46 | );
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Serializer/StringsetTest.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\Serializer\Stringset;
16 |
17 | class StringsetTest extends TestCase
18 | {
19 |
20 | public function setUp()
21 | {
22 | $this->serializer = new Stringset();
23 | }
24 |
25 | public function serializerProvider()
26 | {
27 | return array(
28 | array(array('a', 'b', 'c'), 'a b c '),
29 | array(array('juju', 'molly', 'loulou'), 'juju molly loulou '),
30 | array(array('a-b', 'c', 'd'), 'a-b c d '),
31 | array(array(), ''),
32 | array(null, '')
33 | );
34 | }
35 |
36 | /**
37 | * @dataProvider serializerProvider
38 | */
39 | public function testSerialize($var, $exp)
40 | {
41 | $this->assertEquals(
42 | $exp,
43 | $this->serializer->serialize($var)
44 | );
45 | }
46 |
47 | public function unserializerProvider()
48 | {
49 | return array(
50 | array(array('a', 'b', 'c'), 'a b c ', 0),
51 | array(array('a', 'c'), 'a b c -b -x ', 2),
52 | array(array('c'), 'a b c -b -x -a', 3),
53 | array(null, 'a -a', 1)
54 | );
55 | }
56 |
57 | /**
58 | * @dataProvider unserializerProvider
59 | */
60 | public function testUnserializer($arr, $str, $dirt)
61 | {
62 | $this->assertEquals(
63 | $arr,
64 | $this->serializer->unserialize($str)
65 | );
66 | $this->assertEquals(
67 | $dirt,
68 | $this->serializer->getDirtiness()
69 | );
70 | }
71 |
72 | public function isSerializerProvider()
73 | {
74 | return array(
75 | array(array('a', 'b', 'c'), 'a b c '),
76 | array(array('juju', 'molly', 'loulou'), 'juju molly loulou '),
77 | array(array('a-b', 'c', 'd'), 'a-b c d ')
78 | );
79 | }
80 |
81 | /**
82 | * @dataProvider isSerializerProvider
83 | * @deprecated
84 | */
85 | public function testIsSerialized($var, $str = null)
86 | {
87 | $this->assertFalse(
88 | $this->serializer->isSerialized($var)
89 | );
90 | $this->assertTrue(
91 | $this->serializer->isSerialized($str)
92 | );
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Serializer/TestCase.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests\Serializer;
14 |
15 | use Apix\Cache\tests\TestCase as ApixTestCase;
16 |
17 | class TestCase extends ApixTestCase
18 | {
19 | protected $serializer = null;
20 |
21 | public function serializerProvider()
22 | {
23 | return array(
24 | array('string'),
25 | array(array('foo' => 'bar')),
26 | array(new \stdClass()),
27 | array(' '),
28 | // array(null),
29 | );
30 | }
31 |
32 | /**
33 | * @dataProvider serializerProvider
34 | * @deprecated
35 | */
36 | public function testIsSerialized($var, $str = null)
37 | {
38 | $this->assertFalse(
39 | $this->serializer->isSerialized($var)
40 | );
41 |
42 | $this->assertTrue(
43 | $this->serializer->isSerialized(
44 | $this->serializer->serialize($var)
45 | )
46 | );
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |
8 | *
9 | * @license http://opensource.org/licenses/BSD-3-Clause New BSD License
10 | *
11 | */
12 |
13 | namespace Apix\Cache\tests;
14 |
15 | /**
16 | * Generic TestCase
17 | *
18 | * @package Apix\Cache
19 | * @author Franck Cassedanne
20 | */
21 | class TestCase extends \PHPUnit_Framework_TestCase
22 | {
23 |
24 | protected $options = array(
25 | 'prefix_key' => 'unittest-apix-key:',
26 | 'prefix_tag' => 'unittest-apix-tag:'
27 | );
28 |
29 | public function skipIfMissing($name)
30 | {
31 | if (defined('HHVM_VERSION')) {
32 | switch($name) {
33 | case 'redis':
34 | #case 'mongo':
35 | self::markTestSkipped(
36 | sprintf('`%s` cannot be used with HHVM.', $name)
37 | );
38 | return;
39 |
40 | default:
41 | }
42 | }
43 |
44 | if (!extension_loaded($name)) {
45 | $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
46 | if (
47 | !ini_get('enable_dl')
48 | || !dl($prefix . "$name." . PHP_SHLIB_SUFFIX)) {
49 | self::markTestSkipped(
50 | sprintf('The `%s` extension is required.', $name)
51 | );
52 | }
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/tests/bin/travis-init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | set -o pipefail
4 |
5 | VERSION=`phpenv version-name`
6 |
7 | function pecl_install()
8 | {
9 | local _PKG=$1
10 | local _VERSION=$2
11 |
12 | pecl shell-test $_PKG || (echo "yes" | pecl install $_PKG-$_VERSION)
13 | }
14 |
15 | if [ "${VERSION}" = "hhvm" ]; then
16 | PHPINI=/etc/hhvm/php.ini
17 | else
18 | PHPINI=~/.phpenv/versions/$VERSION/etc/php.ini
19 |
20 | # update PECL
21 | pecl channel-update pecl.php.net
22 |
23 | # install igbinary
24 | pecl_install igbinary 2.0.8
25 |
26 | # install msgpack
27 | if [ "$(expr "${VERSION}" "<" "7.0")" -eq 1 ]
28 | then
29 | pecl_install msgpack 0.5.7
30 | else
31 | pecl_install msgpack 2.0.3
32 | fi
33 | fi
34 |
35 | # enable xdebug
36 | if [[ $TRAVIS_PHP_VERSION =~ ^hhvm ]]
37 | then
38 | echo 'xdebug.enable = On' >> $PHPINI
39 | fi
40 |
41 | #
42 | # APC
43 | #
44 | if [ "$DB" = "apc" ]; then
45 | if [ "${VERSION}" = "hhvm" ] || [ "$(expr "${VERSION}" "<" "5.5")" -eq 1 ]
46 | then
47 | echo "extension = apc.so" >> $PHPINI
48 | elif [ "$(expr "${VERSION}" "<" "7.0")" -eq 1 ]
49 | then
50 | pecl_install apcu 4.0.11
51 | else
52 | pecl_install apcu 5.1.8
53 | fi
54 | echo "apc.enable_cli = 1" >> $PHPINI
55 | fi
56 |
57 | #
58 | # Redis
59 | #
60 | if [ "$DB" = "redis" ]; then
61 | git clone --branch=master --depth=1 git://github.com/nicolasff/phpredis.git phpredis
62 | cd phpredis && phpize && ./configure && make && sudo make install && cd ..
63 | rm -fr phpredis
64 | echo "extension = redis.so" >> $PHPINI
65 | fi
66 |
67 | #
68 | # Mongo DB
69 | #
70 | if [ "$DB" = "mongodb" ]; then
71 | if [ "${VERSION}" != "hhvm" ] && [ "$(expr "${VERSION}" "<" "7.0")" -eq 1 ]
72 | then
73 | echo "extension = mongo.so" >> $PHPINI
74 | else
75 | if [ "${VERSION}" = "hhvm" ]
76 | then
77 | # Instal HHVM
78 | sudo apt-get install -y cmake
79 | git clone git://github.com/facebook/hhvm.git
80 | cd hhvm && git checkout 1da451b && cd - # Tag:3.0.1
81 | export HPHP_HOME=`pwd`/hhvm
82 |
83 | # Install mongo-hhvm-driver
84 | git clone https://github.com/mongodb/mongo-hhvm-driver.git --branch 1.1.3
85 | cd mongo-hhvm-driver
86 | git submodule sync && git submodule update --init --recursive
87 | $HPHP_HOME/hphp/tools/hphpize/hphpize
88 | cmake . && make configlib && make -j 2 && make install
89 | echo "hhvm.dynamic_extensions[mongodb] = mongodb.so" >> $PHPINI
90 | fi
91 | echo "extension = mongodb.so" >> $PHPINI
92 | composer require "mongodb/mongodb=^1.0.0"
93 | fi
94 | fi
95 |
96 | #
97 | # MySQL
98 | #
99 | if [ "$DB" = "mysql" ]; then
100 | mysql -e 'CREATE DATABASE IF NOT EXISTS apix_tests;'
101 | fi
102 |
103 | #
104 | # Postgres
105 | #
106 | if [ "$DB" = "pgsql" ]; then
107 | psql -c 'DROP DATABASE IF EXISTS apix_tests;' -U postgres
108 | psql -c 'CREATE DATABASE apix_tests;' -U postgres
109 | fi
110 |
111 | #
112 | # Memcached
113 | #
114 | if [ "$DB" = "memcached" ]; then
115 | echo "extension = memcached.so" >> $PHPINI
116 | fi
117 |
--------------------------------------------------------------------------------