pax_global_header00006660000000000000000000000064133141626300014511gustar00rootroot0000000000000052 comment=7d7da7fb7574d471904ba357b39bbf110ccdbf66 cache-0.5.0/000077500000000000000000000000001331416263000125565ustar00rootroot00000000000000cache-0.5.0/.gitignore000066400000000000000000000000251331416263000145430ustar00rootroot00000000000000composer.lock vendor cache-0.5.0/.travis.yml000066400000000000000000000005301331416263000146650ustar00rootroot00000000000000language: php php: # - 5.3 # requires old distro, see below - 5.4 - 5.5 - 5.6 - 7 - hhvm # lock distro so new future defaults will not break the build dist: trusty matrix: include: - php: 5.3 dist: precise sudo: false install: - composer install --no-interaction script: - ./vendor/bin/phpunit --coverage-text cache-0.5.0/CHANGELOG.md000066400000000000000000000024771331416263000144010ustar00rootroot00000000000000# Changelog ## 0.5.0 (2018-06-25) * Improve documentation by describing what is expected of a class implementing `CacheInterface`. (#21, #22, #23, #27 by @WyriHaximus) * Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`. (#26 by @clue) * Added support for cache expiration (TTL). (#29 by @clue and @WyriHaximus) * Renamed `remove` to `delete` making it more in line with `PSR-16`. (#30 by @clue) ## 0.4.2 (2017-12-20) * Improve documentation with usage and installation instructions (#10 by @clue) * Improve test suite by adding PHPUnit to `require-dev` and add forward compatibility with PHPUnit 5 and PHPUnit 6 and sanitize Composer autoload paths (#14 by @shaunbramley and #12 and #18 by @clue) ## 0.4.1 (2016-02-25) * Repository maintenance, split off from main repo, improve test suite and documentation * First class support for PHP7 and HHVM (#9 by @clue) * Adjust compatibility to 5.3 (#7 by @clue) ## 0.4.0 (2014-02-02) * BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks * BC break: Update to React/Promise 2.0 * Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 ## 0.3.2 (2013-05-10) * Version bump ## 0.3.0 (2013-04-14) * Version bump ## 0.2.6 (2012-12-26) * Feature: New cache component, used by DNS cache-0.5.0/LICENSE000066400000000000000000000020551331416263000135650ustar00rootroot00000000000000Copyright (c) 2012 Igor Wiedler, Chris Boden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cache-0.5.0/README.md000066400000000000000000000142621331416263000140420ustar00rootroot00000000000000# Cache Component [![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) [![Code Climate](https://codeclimate.com/github/reactphp/cache/badges/gpa.svg)](https://codeclimate.com/github/reactphp/cache) Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). The cache component provides a [Promise](https://github.com/reactphp/promise)-based [`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache) implementation of that. This allows consumers to type hint against the interface and third parties to provide alternate implementations. **Table of Contents** * [Usage](#usage) * [CacheInterface](#cacheinterface) * [get()](#get) * [set()](#set) * [delete()](#delete) * [ArrayCache](#arraycache) * [Common usage](#common-usage) * [Fallback get](#fallback-get) * [Fallback-get-and-set](#fallback-get-and-set) * [Install](#install) * [Tests](#tests) * [License](#license) ## Usage ### CacheInterface The `CacheInterface` describes the main interface of this component. This allows consumers to type hint against the interface and third parties to provide alternate implementations. #### get() The `get(string $key, mixed $default = null): PromiseInterface` method can be used to retrieve an item from the cache. This method will resolve with the cached value on success or with the given `$default` value when no item can be found or when an error occurs. Similarly, an expired cache item (once the time-to-live is expired) is considered a cache miss. ```php $cache ->get('foo') ->then('var_dump'); ``` This example fetches the value of the key `foo` and passes it to the `var_dump` function. You can use any of the composition provided by [promises](https://github.com/reactphp/promise). #### set() The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to store an item in the cache. This method will resolve with `true` on success or `false` when an error occurs. If the cache implementation has to go over the network to store it, it may take a while. The optional `$ttl` parameter sets the maximum time-to-live in seconds for this cache item. If this parameter is omitted (or `null`), the item will stay in the cache for as long as the underlying implementation supports. Trying to access an expired cache item results in a cache miss, see also [`get()`](#get). ```php $cache->set('foo', 'bar', 60); ``` This example eventually sets the value of the key `foo` to `bar`. If it already exists, it is overridden. #### delete() Deletes an item from the cache. This method will resolve with `true` on success or `false` when an error occurs. When no item for `$key` is found in the cache, it also resolves to `true`. If the cache implementation has to go over the network to delete it, it may take a while. ```php $cache->delete('foo'); ``` This example eventually deletes the key `foo` from the cache. As with `set()`, this may not happen instantly and a promise is returned to provide guarantees whether or not the item has been removed from cache. ### ArrayCache The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). ```php $cache = new ArrayCache(); $cache->set('foo', 'bar'); ``` Its constructor accepts an optional `?int $limit` parameter to limit the maximum number of entries to store in the LRU cache. If you add more entries to this instance, it will automatically take care of removing the one that was least recently used (LRU). For example, this snippet will overwrite the first value and only store the last two entries: ```php $cache = new ArrayCache(2); $cache->set('foo', '1'); $cache->set('bar', '2'); $cache->set('baz', '3'); ``` ## Common usage ### Fallback get A common use case of caches is to attempt fetching a cached value and as a fallback retrieve it from the original data source if not found. Here is an example of that: ```php $cache ->get('foo') ->then(function ($result) { if ($result === null) { return getFooFromDb(); } return $result; }) ->then('var_dump'); ``` First an attempt is made to retrieve the value of `foo`. A callback function is registered that will call `getFooFromDb` when the resulting value is null. `getFooFromDb` is a function (can be any PHP callable) that will be called if the key does not exist in the cache. `getFooFromDb` can handle the missing key by returning a promise for the actual value from the database (or any other data source). As a result, this chain will correctly fall back, and provide the value in both cases. ### Fallback get and set To expand on the fallback get example, often you want to set the value on the cache after fetching it from the data source. ```php $cache ->get('foo') ->then(function ($result) { if ($result === null) { return $this->getAndCacheFooFromDb(); } return $result; }) ->then('var_dump'); public function getAndCacheFooFromDb() { return $this->db ->get('foo') ->then(array($this, 'cacheFooFromDb')); } public function cacheFooFromDb($foo) { $this->cache->set('foo', $foo); return $foo; } ``` By using chaining you can easily conditionally cache the value if it is fetched from the database. ## Install The recommended way to install this library is [through Composer](https://getcomposer.org). [New to Composer?](https://getcomposer.org/doc/00-intro.md) This will install the latest supported version: ```bash $ composer require react/cache:^0.5.0 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM. It's *highly recommended to use PHP 7+* for this project. ## Tests To run the test suite, you first need to clone this repo and then install all dependencies [through Composer](https://getcomposer.org): ```bash $ composer install ``` To run the test suite, go to the project root and run: ```bash $ php vendor/bin/phpunit ``` ## License MIT, see [LICENSE file](LICENSE). cache-0.5.0/composer.json000066400000000000000000000007741331416263000153100ustar00rootroot00000000000000{ "name": "react/cache", "description": "Async, Promise-based cache interface for ReactPHP", "keywords": ["cache", "caching", "promise", "ReactPHP"], "license": "MIT", "require": { "php": ">=5.3.0", "react/promise": "~2.0|~1.1" }, "autoload": { "psr-4": { "React\\Cache\\": "src/" } }, "autoload-dev": { "psr-4": { "React\\Tests\\Cache\\": "tests/" } }, "require-dev": { "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" } } cache-0.5.0/phpunit.xml.dist000066400000000000000000000007651331416263000157410ustar00rootroot00000000000000 ./tests/ ./src/ cache-0.5.0/src/000077500000000000000000000000001331416263000133455ustar00rootroot00000000000000cache-0.5.0/src/ArrayCache.php000066400000000000000000000062441331416263000160660ustar00rootroot00000000000000set('foo', 'bar'); * ``` * * Its constructor accepts an optional `?int $limit` parameter to limit the * maximum number of entries to store in the LRU cache. If you add more * entries to this instance, it will automatically take care of removing * the one that was least recently used (LRU). * * For example, this snippet will overwrite the first value and only store * the last two entries: * * ```php * $cache = new ArrayCache(2); * * $cache->set('foo', '1'); * $cache->set('bar', '2'); * $cache->set('baz', '3'); * ``` * * @param int|null $limit maximum number of entries to store in the LRU cache */ public function __construct($limit = null) { $this->limit = $limit; } public function get($key, $default = null) { // delete key if it is already expired => below will detect this as a cache miss if (isset($this->expires[$key]) && $this->expires[$key] < microtime(true)) { unset($this->data[$key], $this->expires[$key]); } if (!array_key_exists($key, $this->data)) { return Promise\resolve($default); } // remove and append to end of array to keep track of LRU info $value = $this->data[$key]; unset($this->data[$key]); $this->data[$key] = $value; return Promise\resolve($value); } public function set($key, $value, $ttl = null) { // unset before setting to ensure this entry will be added to end of array (LRU info) unset($this->data[$key]); $this->data[$key] = $value; // sort expiration times if TTL is given (first will expire first) unset($this->expires[$key]); if ($ttl !== null) { $this->expires[$key] = microtime(true) + $ttl; asort($this->expires); } // ensure size limit is not exceeded or remove first entry from array if ($this->limit !== null && count($this->data) > $this->limit) { // first try to check if there's any expired entry // expiration times are sorted, so we can simply look at the first one reset($this->expires); $key = key($this->expires); // check to see if the first in the list of expiring keys is already expired // if the first key is not expired, we have to overwrite by using LRU info if ($key === null || $this->expires[$key] > microtime(true)) { reset($this->data); $key = key($this->data); } unset($this->data[$key], $this->expires[$key]); } return Promise\resolve(true); } public function delete($key) { unset($this->data[$key], $this->expires[$key]); return Promise\resolve(true); } } cache-0.5.0/src/CacheInterface.php000066400000000000000000000053641331416263000167120ustar00rootroot00000000000000get('foo') * ->then('var_dump'); * ``` * * This example fetches the value of the key `foo` and passes it to the * `var_dump` function. You can use any of the composition provided by * [promises](https://github.com/reactphp/promise). * * @param string $key * @param mixed $default Default value to return for cache miss or null if not given. * @return PromiseInterface */ public function get($key, $default = null); /** * Stores an item in the cache. * * This method will resolve with `true` on success or `false` when an error * occurs. If the cache implementation has to go over the network to store * it, it may take a while. * * The optional `$ttl` parameter sets the maximum time-to-live in seconds * for this cache item. If this parameter is omitted (or `null`), the item * will stay in the cache for as long as the underlying implementation * supports. Trying to access an expired cache item results in a cache miss, * see also [`get()`](#get). * * ```php * $cache->set('foo', 'bar', 60); * ``` * * This example eventually sets the value of the key `foo` to `bar`. If it * already exists, it is overridden. * * @param string $key * @param mixed $value * @param ?float $ttl * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function set($key, $value, $ttl = null); /** * Deletes an item from the cache. * * This method will resolve with `true` on success or `false` when an error * occurs. When no item for `$key` is found in the cache, it also resolves * to `true`. If the cache implementation has to go over the network to * delete it, it may take a while. * * ```php * $cache->delete('foo'); * ``` * * This example eventually deletes the key `foo` from the cache. As with * `set()`, this may not happen instantly and a promise is returned to * provide guarantees whether or not the item has been removed from cache. * * @param string $key * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function delete($key); } cache-0.5.0/tests/000077500000000000000000000000001331416263000137205ustar00rootroot00000000000000cache-0.5.0/tests/ArrayCacheTest.php000066400000000000000000000123531331416263000172770ustar00rootroot00000000000000cache = new ArrayCache(); } /** @test */ public function getShouldResolvePromiseWithNullForNonExistentKey() { $success = $this->createCallableMock(); $success ->expects($this->once()) ->method('__invoke') ->with(null); $this->cache ->get('foo') ->then( $success, $this->expectCallableNever() ); } /** @test */ public function setShouldSetKey() { $setPromise = $this->cache ->set('foo', 'bar'); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') ->with($this->identicalTo(true)); $setPromise->then($mock); $success = $this->createCallableMock(); $success ->expects($this->once()) ->method('__invoke') ->with('bar'); $this->cache ->get('foo') ->then($success); } /** @test */ public function deleteShouldDeleteKey() { $this->cache ->set('foo', 'bar'); $deletePromise = $this->cache ->delete('foo'); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') ->with($this->identicalTo(true)); $deletePromise->then($mock); $this->cache ->get('foo') ->then( $this->expectCallableOnce(), $this->expectCallableNever() ); } public function testGetWillResolveWithNullForCacheMiss() { $this->cache = new ArrayCache(); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } public function testGetWillResolveWithDefaultValueForCacheMiss() { $this->cache = new ArrayCache(); $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar')); } public function testGetWillResolveWithExplicitNullValueForCacheHit() { $this->cache = new ArrayCache(); $this->cache->set('foo', null); $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null)); } public function testLimitSizeToZeroDoesNotStoreAnyData() { $this->cache = new ArrayCache(0); $this->cache->set('foo', 'bar'); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } public function testLimitSizeToOneWillOnlyReturnLastWrite() { $this->cache = new ArrayCache(1); $this->cache->set('foo', '1'); $this->cache->set('bar', '2'); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); $this->cache->get('bar')->then($this->expectCallableOnceWith('2')); } public function testOverwriteWithLimitedSizeWillUpdateLRUInfo() { $this->cache = new ArrayCache(2); $this->cache->set('foo', '1'); $this->cache->set('bar', '2'); $this->cache->set('foo', '3'); $this->cache->set('baz', '4'); $this->cache->get('foo')->then($this->expectCallableOnceWith('3')); $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); $this->cache->get('baz')->then($this->expectCallableOnceWith('4')); } public function testGetWithLimitedSizeWillUpdateLRUInfo() { $this->cache = new ArrayCache(2); $this->cache->set('foo', '1'); $this->cache->set('bar', '2'); $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); $this->cache->set('baz', '3'); $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); $this->cache->get('baz')->then($this->expectCallableOnceWith('3')); } public function testGetWillResolveWithValueIfItemIsNotExpired() { $this->cache = new ArrayCache(); $this->cache->set('foo', '1', 10); $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); } public function testGetWillResolveWithDefaultIfItemIsExpired() { $this->cache = new ArrayCache(); $this->cache->set('foo', '1', 0); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } public function testSetWillOverwritOldestItemIfNoEntryIsExpired() { $this->cache = new ArrayCache(2); $this->cache->set('foo', '1', 10); $this->cache->set('bar', '2', 20); $this->cache->set('baz', '3', 30); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired() { $this->cache = new ArrayCache(2); $this->cache->set('foo', '1', 10); $this->cache->set('bar', '2', 0); $this->cache->set('baz', '3', 30); $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); } } cache-0.5.0/tests/CallableStub.php000066400000000000000000000001471331416263000167700ustar00rootroot00000000000000createCallableMock(); $mock ->expects($this->exactly($amount)) ->method('__invoke'); return $mock; } protected function expectCallableOnce() { $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke'); return $mock; } protected function expectCallableOnceWith($param) { $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') ->with($param); return $mock; } protected function expectCallableNever() { $mock = $this->createCallableMock(); $mock ->expects($this->never()) ->method('__invoke'); return $mock; } protected function createCallableMock() { return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock(); } }