From 7756465d95197ddc21a0ecba732e5e50fb89eadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 7 May 2014 16:33:59 +0200 Subject: [PATCH 01/78] Move tests to each component --- tests/ArrayCacheTest.php | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/ArrayCacheTest.php diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php new file mode 100644 index 0000000..bbf5042 --- /dev/null +++ b/tests/ArrayCacheTest.php @@ -0,0 +1,61 @@ +cache = new ArrayCache(); + } + + /** @test */ + public function getShouldRejectPromiseForNonExistentKey() + { + $this->cache + ->get('foo') + ->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function setShouldSetKey() + { + $this->cache + ->set('foo', 'bar'); + + $success = $this->createCallableMock(); + $success + ->expects($this->once()) + ->method('__invoke') + ->with('bar'); + + $this->cache + ->get('foo') + ->then($success); + } + + /** @test */ + public function removeShouldRemoveKey() + { + $this->cache + ->set('foo', 'bar'); + + $this->cache + ->remove('foo'); + + $this->cache + ->get('foo') + ->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } +} From 6d3bbbe188fa07e6475de610d5a66eb950ffcdd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 7 May 2014 17:35:03 +0200 Subject: [PATCH 02/78] Make components' tests run on their own and from main repo. Each component has dedicated test config and bootstrap. Duplication of parts of the skeleton is not ideal, but helps to reduce dependencies between each test suite. Also, this eases the future subtree split. --- phpunit.xml.dist | 25 ++++++++++++++++++++++++ tests/ArrayCacheTest.php | 1 - tests/CallableStub.php | 10 ++++++++++ tests/TestCase.php | 41 ++++++++++++++++++++++++++++++++++++++++ tests/bootstrap.php | 7 +++++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml.dist create mode 100644 tests/CallableStub.php create mode 100644 tests/TestCase.php create mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..cba6d4d --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/ + + + + + + ./src/ + + + diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index bbf5042..eec3739 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -3,7 +3,6 @@ namespace React\Tests\Cache; use React\Cache\ArrayCache; -use React\Tests\Socket\TestCase; class ArrayCacheTest extends TestCase { diff --git a/tests/CallableStub.php b/tests/CallableStub.php new file mode 100644 index 0000000..2f547cd --- /dev/null +++ b/tests/CallableStub.php @@ -0,0 +1,10 @@ +createCallableMock(); + $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 expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + return $this->getMock('React\Tests\Cache\CallableStub'); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..c6f536d --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,7 @@ +addPsr4('React\\Tests\\Cache\\', __DIR__); From a35b6d6b3a3cedc55b847ac59e9f73b04fd5cbc2 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 25 May 2014 09:51:40 -0400 Subject: [PATCH 03/78] Prep repo as standalone component --- .gitignore | 2 ++ .travis.yml | 16 ++++++++++++++++ README.md | 16 ++++++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 .gitignore create mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..502c1d3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + +matrix: + allow_failures: + - php: hhvm + +before_script: + - composer install --dev --prefer-source + +script: php vendor/bin/phpunit --coverage-text diff --git a/README.md b/README.md index 0acac5e..c16e60a 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ against the interface and third parties to provide alternate implementations. ## Basic usage ### get - +```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 @@ -21,8 +22,9 @@ This example fetches the value of the key `foo` and passes it to the If the key `foo` does not exist, the promise will be rejected. ### set - +```php $cache->set('foo', 'bar'); +``` This example eventually sets the value of the key `foo` to `bar`. If it already exists, it is overridden. No guarantees are made as to when the cache @@ -30,8 +32,9 @@ value is set. If the cache implementation has to go over the network to store it, it may take a while. ### remove - +```php $cache->remove('foo'); +``` This example eventually removes the key `foo` from the cache. As with `set`, this may not happen instantly. @@ -43,11 +46,12 @@ this may not happen instantly. 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(null, 'getFooFromDb') ->then('var_dump'); +``` First an attempt is made to retrieve the value of `foo`. A promise rejection handler of the function `getFooFromDb` is registered. `getFooFromDb` is a @@ -62,7 +66,7 @@ chain will correctly fall back, and provide the value in both cases. 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(null, array($this, 'getAndCacheFooFromDb')) @@ -81,6 +85,6 @@ cache after fetching it from the data source. return $foo; } - +``` By using chaining you can easily conditionally cache the value if it is fetched from the database. From 9ecccc67cd2d5178036c3b3c250a81b22058f3bd Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 25 May 2014 09:54:53 -0400 Subject: [PATCH 04/78] Travis --- .travis.yml | 2 -- README.md | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 502c1d3..525cdc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,5 +12,3 @@ matrix: before_script: - composer install --dev --prefer-source - -script: php vendor/bin/phpunit --coverage-text diff --git a/README.md b/README.md index c16e60a..0dc222a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Cache Component +[![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) + Promised cache interface. The cache component provides a promise-based cache interface and an in-memory From 9bc907fc867ff85c64b633cd75aa419bfbb019c1 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 25 May 2014 10:03:05 -0400 Subject: [PATCH 05/78] Update directory structure --- composer.json | 4 ++-- ArrayCache.php => src/ArrayCache.php | 0 CacheInterface.php => src/CacheInterface.php | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename ArrayCache.php => src/ArrayCache.php (100%) rename CacheInterface.php => src/CacheInterface.php (100%) diff --git a/composer.json b/composer.json index 5446a08..bb3031e 100644 --- a/composer.json +++ b/composer.json @@ -8,11 +8,11 @@ "react/promise": "~2.0" }, "autoload": { - "psr-4": { "React\\Cache\\": "" } + "psr-4": { "React\\Cache\\": "src\\" } }, "extra": { "branch-alias": { - "dev-master": "0.4-dev" + "dev-master": "0.5-dev" } } } diff --git a/ArrayCache.php b/src/ArrayCache.php similarity index 100% rename from ArrayCache.php rename to src/ArrayCache.php diff --git a/CacheInterface.php b/src/CacheInterface.php similarity index 100% rename from CacheInterface.php rename to src/CacheInterface.php From bb9f6559469324fe2684cef2da8f8db8a456a81b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 25 May 2014 12:23:46 -0400 Subject: [PATCH 06/78] Adjusted parent test bootstrap loader path --- tests/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c6f536d..108d8e8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,6 +2,6 @@ $loader = @include __DIR__ . '/../vendor/autoload.php'; if (!$loader) { - $loader = require __DIR__ . '/../../../vendor/autoload.php'; + $loader = require __DIR__ . '/../../../../vendor/autoload.php'; } $loader->addPsr4('React\\Tests\\Cache\\', __DIR__); From 2d4372ff44ed0e4cc9fd86a3052730d45aac865b Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 1 Jun 2014 08:50:14 -0400 Subject: [PATCH 07/78] Added license file --- LICENSE | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a808108 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (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. From e188eba4d6d18735db8c4ced9574c2457c3a9a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 7 Jun 2014 02:10:22 +0200 Subject: [PATCH 08/78] Add CHANGELOG Source: https://github.com/reactphp/react/blob/a6de34d61f68adebd3cc3b855268a5f1475749b8/CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2b4c4d5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +## 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) + +?? + +## 0.3.0 (2013-04-14) + +?? + +## 0.2.6 (2012-12-26) + +* Feature: New cache component, used by DNS From 7ce67274539eda7264a294706841ff402eff52da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 9 Jun 2014 03:08:23 +0200 Subject: [PATCH 09/78] Add bumped versions to changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4c4d5..43b80f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,11 @@ ## 0.3.2 (2013-05-10) -?? +* Version bump ## 0.3.0 (2013-04-14) -?? +* Version bump ## 0.2.6 (2012-12-26) From 0eb43b947d48e37a2af4524e55994d904864e7e8 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 12 Jun 2014 15:34:05 +0200 Subject: [PATCH 10/78] Show test coverage directly after running test --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 525cdc6..d2fb756 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,3 +12,6 @@ matrix: before_script: - composer install --dev --prefer-source + +script: + - phpunit --coverage-text From 488700bc09543db44ede43d64f2ad4c1077878aa Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 12 Jun 2014 22:10:21 +0200 Subject: [PATCH 11/78] Added phpunit 4 to require-dev and using it in .travis.yml --- .travis.yml | 2 +- composer.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2fb756..ba7ddc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,4 @@ before_script: - composer install --dev --prefer-source script: - - phpunit --coverage-text + - php vendor/bin/phpunit --coverage-text diff --git a/composer.json b/composer.json index bb3031e..5e6076f 100644 --- a/composer.json +++ b/composer.json @@ -14,5 +14,8 @@ "branch-alias": { "dev-master": "0.5-dev" } + }, + "require-dev": { + "phpunit/phpunit": "4.*" } } From 1b0de4a040cbc69470f8d8462da2fc256771408c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 16 Jun 2014 20:56:40 +0200 Subject: [PATCH 12/78] Reverted phpunit addition in require-dev --- .travis.yml | 2 +- composer.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba7ddc9..d2fb756 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,4 +14,4 @@ before_script: - composer install --dev --prefer-source script: - - php vendor/bin/phpunit --coverage-text + - phpunit --coverage-text diff --git a/composer.json b/composer.json index 5e6076f..bb3031e 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,5 @@ "branch-alias": { "dev-master": "0.5-dev" } - }, - "require-dev": { - "phpunit/phpunit": "4.*" } } From c8dd30b658b2554e2b1c2126b95cb545d362d7ef Mon Sep 17 00:00:00 2001 From: e3betht Date: Thu, 18 Dec 2014 12:15:27 -0600 Subject: [PATCH 13/78] Adding Code Climate badge to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dc222a..b582385 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cache Component -[![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) +[![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) Promised cache interface. From bc5ff92846a006ed4f37452efdb615d0b033629b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 15 Apr 2015 21:06:04 +0200 Subject: [PATCH 14/78] Test against PHP7 --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index d2fb756..ab158b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,16 @@ php: - 5.4 - 5.5 - 5.6 + - 7 - hhvm + - hhvm-nightly matrix: allow_failures: + - php: 7 - php: hhvm + - php: hhvm-nightly + fast_finish: true before_script: - composer install --dev --prefer-source From 4c3160d1b9f63759801dcefedf46ea2eff35168d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 3 Nov 2015 00:24:22 +0100 Subject: [PATCH 15/78] Compatibility with legacy PHP 5.3 --- .travis.yml | 1 + composer.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab158b7..6feb0a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php php: + - 5.3 - 5.4 - 5.5 - 5.6 diff --git a/composer.json b/composer.json index bb3031e..b80c88b 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,8 @@ "keywords": ["cache"], "license": "MIT", "require": { - "php": ">=5.4.0", - "react/promise": "~2.0" + "php": ">=5.3.0", + "react/promise": "~2.0|~1.1" }, "autoload": { "psr-4": { "React\\Cache\\": "src\\" } From 55a1d7d3291033a931d75b2e8c08458c9873f835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 4 Nov 2015 00:31:31 +0100 Subject: [PATCH 16/78] Consistent reporting of test failures and conciser test setup --- .travis.yml | 12 ++---------- composer.json | 6 ++---- phpunit.xml.dist | 9 ++------- tests/bootstrap.php | 7 ------- 4 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 tests/bootstrap.php diff --git a/.travis.yml b/.travis.yml index ab158b7..c6a74b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,17 +6,9 @@ php: - 5.6 - 7 - hhvm - - hhvm-nightly -matrix: - allow_failures: - - php: 7 - - php: hhvm - - php: hhvm-nightly - fast_finish: true - -before_script: - - composer install --dev --prefer-source +install: + - composer install --prefer-source --no-interaction script: - phpunit --coverage-text diff --git a/composer.json b/composer.json index bb3031e..56c6ec4 100644 --- a/composer.json +++ b/composer.json @@ -10,9 +10,7 @@ "autoload": { "psr-4": { "React\\Cache\\": "src\\" } }, - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } + "autoload-dev": { + "psr-4": { "React\\Tests\\Cache\\": "tests\\" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cba6d4d..d02182f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,10 @@ - diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 108d8e8..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,7 +0,0 @@ -addPsr4('React\\Tests\\Cache\\', __DIR__); From 558f614891341b1d817a8cdf9a358948ec49638f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:17:16 +0100 Subject: [PATCH 17/78] Prepare v0.4.1 release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b80f0..b16b555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 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 From 89beaf783992b1ce64aec0eeaefc4b863a7d28cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:39:48 +0100 Subject: [PATCH 18/78] Consistent formatting for examples --- README.md | 58 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b582385..4b1ee3f 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ against the interface and third parties to provide alternate implementations. ## Basic usage ### get + ```php - $cache - ->get('foo') - ->then('var_dump'); +$cache + ->get('foo') + ->then('var_dump'); ``` This example fetches the value of the key `foo` and passes it to the @@ -24,8 +25,9 @@ This example fetches the value of the key `foo` and passes it to the If the key `foo` does not exist, the promise will be rejected. ### set + ```php - $cache->set('foo', 'bar'); +$cache->set('foo', 'bar'); ``` This example eventually sets the value of the key `foo` to `bar`. If it @@ -34,8 +36,9 @@ value is set. If the cache implementation has to go over the network to store it, it may take a while. ### remove + ```php - $cache->remove('foo'); +$cache->remove('foo'); ``` This example eventually removes the key `foo` from the cache. As with `set`, @@ -48,11 +51,12 @@ this may not happen instantly. 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(null, 'getFooFromDb') - ->then('var_dump'); +$cache + ->get('foo') + ->then(null, 'getFooFromDb') + ->then('var_dump'); ``` First an attempt is made to retrieve the value of `foo`. A promise rejection @@ -68,25 +72,27 @@ chain will correctly fall back, and provide the value in both cases. 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 +$cache + ->get('foo') + ->then(null, array($this, 'getAndCacheFooFromDb')) + ->then('var_dump'); + +public function getAndCacheFooFromDb() +{ + return $this->db ->get('foo') - ->then(null, array($this, 'getAndCacheFooFromDb')) - ->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; - } + ->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. From 40013c9a0104769049b2e3f6f8b8fd7ff7ff75e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:41:42 +0100 Subject: [PATCH 19/78] Add TOC --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4b1ee3f..2167115 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,16 @@ The cache component provides a promise-based cache interface and an in-memory `ArrayCache` implementation of that. This allows consumers to type hint against the interface and third parties to provide alternate implementations. +**Table of Contents** + +* [Basic usage](#basic-usage) + * [get](#get) + * [set](#set) + * [remove](#remove) +* [Common usage](#common-usage) + * [Fallback get](#fallback-get) + * [Fallback-get-and-set](#fallback-get-and-set) + ## Basic usage ### get From 9e288c67a6c56451ad6b2acd0f19565250a8fdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:49:17 +0100 Subject: [PATCH 20/78] Documentation for CacheInterface and ArrayCache --- README.md | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2167115..65cf6a1 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,32 @@ Promised cache interface. -The cache component provides a promise-based cache interface and an in-memory -`ArrayCache` implementation of that. This allows consumers to type hint -against the interface and third parties to provide alternate implementations. +The cache component provides a 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** -* [Basic usage](#basic-usage) - * [get](#get) - * [set](#set) - * [remove](#remove) +* [Usage](#usage) + * [CacheInterface](#cacheinterface) + * [get()](#get) + * [set()](#set) + * [remove()](#remove) + * [ArrayCache](#arraycache) * [Common usage](#common-usage) * [Fallback get](#fallback-get) * [Fallback-get-and-set](#fallback-get-and-set) -## Basic usage +## Usage -### get +### 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() ```php $cache @@ -34,7 +43,7 @@ This example fetches the value of the key `foo` and passes it to the If the key `foo` does not exist, the promise will be rejected. -### set +#### set() ```php $cache->set('foo', 'bar'); @@ -45,7 +54,7 @@ already exists, it is overridden. No guarantees are made as to when the cache value is set. If the cache implementation has to go over the network to store it, it may take a while. -### remove +#### remove() ```php $cache->remove('foo'); @@ -54,6 +63,17 @@ $cache->remove('foo'); This example eventually removes the key `foo` from the cache. As with `set`, this may not happen instantly. +### ArrayCache + +The `ArrayCache` provides an in-memory implementation of the +[`CacheInterface`](#cacheinterface). + +```php +$cache = new ArrayCache(); + +$cache->set('foo', 'bar'); +``` + ## Common usage ### Fallback get From 4ec4671c91a726a297947e5f59e9e5c55aa64342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:51:30 +0100 Subject: [PATCH 21/78] Add installation and license instructions --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 65cf6a1..eeb08df 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ provide alternate implementations. * [Common usage](#common-usage) * [Fallback get](#fallback-get) * [Fallback-get-and-set](#fallback-get-and-set) +* [Install](#install) +* [License](#license) ## Usage @@ -126,3 +128,26 @@ public function cacheFooFromDb($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](http://getcomposer.org). +[New to Composer?](http://getcomposer.org/doc/00-intro.md) + +This will install the latest supported version: + +```bash +$ composer require react/cache:~0.4.0 +``` + +If you care a lot about BC, you may also want to look into supporting legacy versions: + +```bash +$ composer require "react/cache:~0.4.0|~0.3.0" +``` + +More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md). + +## License + +MIT, see [LICENSE file](LICENSE). From a79879b36cbf5f3d88714dac78105c4757a76093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 25 Feb 2016 19:59:08 +0100 Subject: [PATCH 22/78] Add links to promise lib --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eeb08df..c426e21 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@ [![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) -Promised cache interface. +Async, [Promise](https://github.com/reactphp/promise)-based cache interface. -The cache component provides a promise-based [`CacheInterface`](#cacheinterface) -and an in-memory [`ArrayCache`](#arraycache) implementation of that. +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. From 0cb85cd94ddce0f8da2c096a3b755b794bb4f4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 26 Dec 2016 23:36:54 +0100 Subject: [PATCH 23/78] Fix autoload paths --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 238b40c..bd3a696 100644 --- a/composer.json +++ b/composer.json @@ -8,9 +8,9 @@ "react/promise": "~2.0|~1.1" }, "autoload": { - "psr-4": { "React\\Cache\\": "src\\" } + "psr-4": { "React\\Cache\\": "src/" } }, "autoload-dev": { - "psr-4": { "React\\Tests\\Cache\\": "tests\\" } + "psr-4": { "React\\Tests\\Cache\\": "tests/" } } } From 1910991463fd6e31eb46341e29ba053846770466 Mon Sep 17 00:00:00 2001 From: Shaun Bramley Date: Sat, 14 Jan 2017 17:09:24 -0500 Subject: [PATCH 24/78] add phpunit 4.8 to require-dev, force travisci to use local phpunit --- .travis.yml | 2 +- composer.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b60bad2..03b7ecb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ install: - composer install --prefer-source --no-interaction script: - - phpunit --coverage-text + - ./vendor/bin/phpunit --coverage-text diff --git a/composer.json b/composer.json index bd3a696..854099f 100644 --- a/composer.json +++ b/composer.json @@ -12,5 +12,8 @@ }, "autoload-dev": { "psr-4": { "React\\Tests\\Cache\\": "tests/" } + }, + "require-dev": { + "phpunit/phpunit": "~4.8" } } From efb6d7f4a75b1fb1d21af869a600360e742bc476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 20 Dec 2017 15:21:10 +0100 Subject: [PATCH 25/78] Forward compatibility with PHPUnit 5 and PHPUnit 6 --- README.md | 16 ++++++++++++++++ composer.json | 2 +- tests/TestCase.php | 6 ++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c426e21..03f5eda 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ provide alternate implementations. * [Fallback get](#fallback-get) * [Fallback-get-and-set](#fallback-get-and-set) * [Install](#install) +* [Tests](#tests) * [License](#license) ## Usage @@ -150,6 +151,21 @@ $ composer require "react/cache:~0.4.0|~0.3.0" More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md). +## 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). diff --git a/composer.json b/composer.json index 854099f..9a8bb33 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,6 @@ "psr-4": { "React\\Tests\\Cache\\": "tests/" } }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 93a04fb..aa449f2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,7 +2,9 @@ namespace React\Tests\Cache; -class TestCase extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase as BaseTestCase; + +class TestCase extends BaseTestCase { protected function expectCallableExactly($amount) { @@ -36,6 +38,6 @@ protected function expectCallableNever() protected function createCallableMock() { - return $this->getMock('React\Tests\Cache\CallableStub'); + return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock(); } } From 7a23d51a1a8c3fbadf8c28b00835542c1844cbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 20 Dec 2017 15:24:13 +0100 Subject: [PATCH 26/78] Lock Travis distro so new defaults will not break the build --- .travis.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03b7ecb..290df75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,25 @@ language: php php: - - 5.3 +# - 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 --prefer-source --no-interaction + - composer install --no-interaction script: - ./vendor/bin/phpunit --coverage-text From 75494f26b4ef089db9bf8c90b63c296246e099e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 20 Dec 2017 17:47:13 +0100 Subject: [PATCH 27/78] Prepare v0.4.2 release --- CHANGELOG.md | 10 ++++++++++ README.md | 20 ++++++++++---------- composer.json | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b16b555..19d1801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 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 diff --git a/README.md b/README.md index 03f5eda..70ad40a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![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. +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 @@ -134,22 +135,21 @@ fetched from the database. ## Install -The recommended way to install this library is [through Composer](http://getcomposer.org). -[New to Composer?](http://getcomposer.org/doc/00-intro.md) +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.4.0 +$ composer require react/cache:^0.4.2 ``` -If you care a lot about BC, you may also want to look into supporting legacy versions: +See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. -```bash -$ composer require "react/cache:~0.4.0|~0.3.0" -``` - -More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md). +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 diff --git a/composer.json b/composer.json index 9a8bb33..51573b6 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "react/cache", - "description": "Async caching.", - "keywords": ["cache"], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": ["cache", "caching", "promise", "ReactPHP"], "license": "MIT", "require": { "php": ">=5.3.0", From 37ee8054a0bbe44c90c0d0a9ba792479a1127b8a Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 3 Feb 2018 17:56:20 +0100 Subject: [PATCH 28/78] Describe current behavior on the cache interface --- src/CacheInterface.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/CacheInterface.php b/src/CacheInterface.php index fd5f2d5..afc2418 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -2,12 +2,33 @@ namespace React\Cache; +use React\Promise\PromiseInterface; + interface CacheInterface { - // @return React\Promise\PromiseInterface + /** + * Retrieve an item from the cache, resolves with its value on + * success or rejects when no item can be found. + * + * @param string $key + * @return PromiseInterface + */ public function get($key); + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ public function set($key, $value); + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ public function remove($key); } From a9400f3f354813be6163a4ea86f788d173a7dd13 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 5 Feb 2018 22:59:47 +0100 Subject: [PATCH 29/78] Failing get should resolve with null --- README.md | 26 +++++++++++++++++++------- src/ArrayCache.php | 2 +- src/CacheInterface.php | 2 +- tests/ArrayCacheTest.php | 16 +++++++++++----- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 70ad40a..8ccaf89 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ 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). -If the key `foo` does not exist, the promise will be rejected. +If the key `foo` does not exist, the promise will be fulfilled with `null` as value. #### set() @@ -91,14 +91,20 @@ example of that: ```php $cache ->get('foo') - ->then(null, 'getFooFromDb') + ->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 promise rejection -handler of the function `getFooFromDb` is registered. `getFooFromDb` is a -function (can be any PHP callable) that will be called if the key does not -exist in the cache. +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 @@ -112,7 +118,13 @@ cache after fetching it from the data source. ```php $cache ->get('foo') - ->then(null, array($this, 'getAndCacheFooFromDb')) + ->then(function ($result) { + if ($result === null) { + return $this->getAndCacheFooFromDb(); + } + + return $result; + }) ->then('var_dump'); public function getAndCacheFooFromDb() diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 03dcc15..6da1574 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -11,7 +11,7 @@ class ArrayCache implements CacheInterface public function get($key) { if (!isset($this->data[$key])) { - return Promise\reject(); + return Promise\resolve(null); } return Promise\resolve($this->data[$key]); diff --git a/src/CacheInterface.php b/src/CacheInterface.php index afc2418..c4f5a2f 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -8,7 +8,7 @@ interface CacheInterface { /** * Retrieve an item from the cache, resolves with its value on - * success or rejects when no item can be found. + * success or null when no item can be found. * * @param string $key * @return PromiseInterface diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index eec3739..9ebed3c 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -14,13 +14,19 @@ public function setUp() } /** @test */ - public function getShouldRejectPromiseForNonExistentKey() + public function getShouldResolvePromiseWithNullForNonExistentKey() { + $success = $this->createCallableMock(); + $success + ->expects($this->once()) + ->method('__invoke') + ->with(null); + $this->cache ->get('foo') ->then( - $this->expectCallableNever(), - $this->expectCallableOnce() + $success, + $this->expectCallableNever() ); } @@ -53,8 +59,8 @@ public function removeShouldRemoveKey() $this->cache ->get('foo') ->then( - $this->expectCallableNever(), - $this->expectCallableOnce() + $this->expectCallableOnce(), + $this->expectCallableNever() ); } } From 0b2326af8b9c647992fc7092f523cd1fc4563c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Mrozi=C5=84ski?= Date: Fri, 3 Mar 2017 20:20:28 +0100 Subject: [PATCH 30/78] Return Promise for set and remove --- src/ArrayCache.php | 2 ++ src/CacheInterface.php | 3 ++- tests/ArrayCacheTest.php | 23 +++++++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 6da1574..0fc0c81 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -20,10 +20,12 @@ public function get($key) public function set($key, $value) { $this->data[$key] = $value; + return new Promise\FulfilledPromise(true); } public function remove($key) { unset($this->data[$key]); + return new Promise\FulfilledPromise(true); } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index c4f5a2f..6c2115f 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -20,7 +20,7 @@ public function get($key); * * @param string $key * @param mixed $value - * @return void + * @return PromiseInterface */ public function set($key, $value); @@ -29,6 +29,7 @@ public function set($key, $value); * * @param string $key * @return void + * @return PromiseInterface */ public function remove($key); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 9ebed3c..f98fc0e 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -6,6 +6,9 @@ class ArrayCacheTest extends TestCase { + /** + * @var ArrayCache + */ private $cache; public function setUp() @@ -33,9 +36,17 @@ public function getShouldResolvePromiseWithNullForNonExistentKey() /** @test */ public function setShouldSetKey() { - $this->cache + $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()) @@ -53,9 +64,17 @@ public function removeShouldRemoveKey() $this->cache ->set('foo', 'bar'); - $this->cache + $removePromise = $this->cache ->remove('foo'); + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(true)); + + $removePromise->then($mock); + $this->cache ->get('foo') ->then( From 584e3c23721738de1ad4384bfb41d95bb16da75d Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 3 Feb 2018 17:56:20 +0100 Subject: [PATCH 31/78] Describe current behavior on the cache interface --- README.md | 11 +++++++---- src/ArrayCache.php | 6 +++--- src/CacheInterface.php | 14 ++++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8ccaf89..2545cda 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ 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). -If the key `foo` does not exist, the promise will be fulfilled with `null` as value. +If the key `foo` does not exist, the promise will be fulfilled with `null` as value. On +any error it will also resolve with `null`. #### set() @@ -56,8 +57,9 @@ $cache->set('foo', 'bar'); ``` This example eventually sets the value of the key `foo` to `bar`. If it -already exists, it is overridden. No guarantees are made as to when the cache -value is set. If the cache implementation has to go over the network to store +already exists, it is overridden. To provide guarantees as to when the cache +value is set a promise is returned. The promise will fulfill with `true` on success +or `false` on error. If the cache implementation has to go over the network to store it, it may take a while. #### remove() @@ -67,7 +69,8 @@ $cache->remove('foo'); ``` This example eventually removes the key `foo` from the cache. As with `set`, -this may not happen instantly. +this may not happen instantly and a promise is returned to provide guarantees whether +or not the item has been removed from cache. ### ArrayCache diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 0fc0c81..f4bf9e8 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -11,7 +11,7 @@ class ArrayCache implements CacheInterface public function get($key) { if (!isset($this->data[$key])) { - return Promise\resolve(null); + return Promise\resolve(); } return Promise\resolve($this->data[$key]); @@ -20,12 +20,12 @@ public function get($key) public function set($key, $value) { $this->data[$key] = $value; - return new Promise\FulfilledPromise(true); + return Promise\resolve(true); } public function remove($key) { unset($this->data[$key]); - return new Promise\FulfilledPromise(true); + return Promise\resolve(true); } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 6c2115f..da6661f 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -8,7 +8,7 @@ interface CacheInterface { /** * Retrieve an item from the cache, resolves with its value on - * success or null when no item can be found. + * success or null when no item can be found or when an error occurs. * * @param string $key * @return PromiseInterface @@ -16,20 +16,22 @@ interface CacheInterface public function get($key); /** - * Store an item in the cache. + * Store an item in the cache, returns a promise which resolves to true on success or + * false on error. * * @param string $key * @param mixed $value - * @return PromiseInterface + * @return PromiseInterface Returns a promise which resolves to true on success of false on error */ public function set($key, $value); /** - * Remove an item from the cache. + * Remove an item from the cache, returns a promise which resolves to true on success or + * false on error. When the $key isn't found in the cache it also + * resolves true. * * @param string $key - * @return void - * @return PromiseInterface + * @return PromiseInterface Returns a promise which resolves to true on success of false on error */ public function remove($key); } From c3322972478e28c23de936eca96b7843ae787926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 1 Mar 2018 20:37:11 +0100 Subject: [PATCH 32/78] Simple LRU implementation for ArrayCache --- README.md | 19 ++++++++++++++-- src/ArrayCache.php | 49 +++++++++++++++++++++++++++++++++++++++- tests/ArrayCacheTest.php | 48 +++++++++++++++++++++++++++++++++++++++ tests/TestCase.php | 11 +++++++++ 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2545cda..297c9de 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,7 @@ or not the item has been removed from cache. ### ArrayCache -The `ArrayCache` provides an in-memory implementation of the -[`CacheInterface`](#cacheinterface). +The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). ```php $cache = new ArrayCache(); @@ -83,6 +82,22 @@ $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 diff --git a/src/ArrayCache.php b/src/ArrayCache.php index f4bf9e8..14a4057 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -6,20 +6,67 @@ class ArrayCache implements CacheInterface { + private $limit; private $data = array(); + /** + * 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'); + * ``` + * + * @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) { if (!isset($this->data[$key])) { return Promise\resolve(); } - return Promise\resolve($this->data[$key]); + // 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) { + // unset before setting to ensure this entry will be added to end of array + unset($this->data[$key]); $this->data[$key] = $value; + + // ensure size limit is not exceeded or remove first entry from array + if ($this->limit !== null && count($this->data) > $this->limit) { + reset($this->data); + unset($this->data[key($this->data)]); + } + return Promise\resolve(true); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index f98fc0e..eb570a5 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -82,4 +82,52 @@ public function removeShouldRemoveKey() $this->expectCallableNever() ); } + + 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')); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index aa449f2..5d31a0e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -26,6 +26,17 @@ protected function expectCallableOnce() 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(); From fb5b8745bdffc6f970ddfa11ba6d96a79456bfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 27 Mar 2018 09:52:58 +0200 Subject: [PATCH 33/78] Add $default to return instead of null on cache miss for get() --- README.md | 9 ++++++--- src/ArrayCache.php | 6 +++--- src/CacheInterface.php | 19 ++++++++++++++++--- tests/ArrayCacheTest.php | 22 ++++++++++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 297c9de..6770ec1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ provide alternate implementations. #### get() +The `get(string $key, mixed $default = null): PromiseInterfae` 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. + ```php $cache ->get('foo') @@ -47,9 +53,6 @@ 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). -If the key `foo` does not exist, the promise will be fulfilled with `null` as value. On -any error it will also resolve with `null`. - #### set() ```php diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 14a4057..7d6f75f 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -41,10 +41,10 @@ public function __construct($limit = null) $this->limit = $limit; } - public function get($key) + public function get($key, $default = null) { - if (!isset($this->data[$key])) { - return Promise\resolve(); + if (!array_key_exists($key, $this->data)) { + return Promise\resolve($default); } // remove and append to end of array to keep track of LRU info diff --git a/src/CacheInterface.php b/src/CacheInterface.php index da6661f..3832fac 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -7,13 +7,26 @@ interface CacheInterface { /** - * Retrieve an item from the cache, resolves with its value on - * success or null when no item can be found or when an error occurs. + * Retrieves 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. + * + * ```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). * * @param string $key + * @param mixed $default Default value to return for cache miss or null if not given. * @return PromiseInterface */ - public function get($key); + public function get($key, $default = null); /** * Store an item in the cache, returns a promise which resolves to true on success or diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index eb570a5..bacf448 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -83,6 +83,28 @@ public function removeShouldRemoveKey() ); } + 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); From 65e95c28179666b7d690425230bb00131941f60b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 30 Mar 2018 07:48:39 +0200 Subject: [PATCH 34/78] TTL skeleton with expiration queueing using SplPriorityQueue --- README.md | 5 +++- src/ArrayCache.php | 56 ++++++++++++++++++++++++++++++++++------ src/CacheInterface.php | 3 ++- tests/ArrayCacheTest.php | 43 ++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6770ec1..67874f4 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,13 @@ 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). +If the key `foo` does not exist or when the TTL has passed, the promise will +be fulfilled with `null` as value. On any error it will also resolve with `null`. + #### set() ```php -$cache->set('foo', 'bar'); +$cache->set('foo', 'bar', 60); ``` This example eventually sets the value of the key `foo` to `bar`. If it diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 7d6f75f..83f3e34 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -3,11 +3,18 @@ namespace React\Cache; use React\Promise; +use SplPriorityQueue; class ArrayCache implements CacheInterface { private $limit; private $data = array(); + private $expires = array(); + + /** + * @var SplPriorityQueue + */ + private $expiresQueue; /** * The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). @@ -39,10 +46,16 @@ class ArrayCache implements CacheInterface public function __construct($limit = null) { $this->limit = $limit; + $this->expiresQueue = new SplPriorityQueue(); + $this->expiresQueue->setExtractFlags(SplPriorityQueue::EXTR_BOTH); } public function get($key, $default = null) { + if (array_key_exists($key, $this->expires)) { + $this->garbageCollection(); + } + if (!array_key_exists($key, $this->data)) { return Promise\resolve($default); } @@ -51,28 +64,55 @@ public function get($key, $default = null) $value = $this->data[$key]; unset($this->data[$key]); $this->data[$key] = $value; - return Promise\resolve($value); } - public function set($key, $value) + public function set($key, $value, $ttl = null) { + $expires = null; + + if (is_int($ttl)) { + $this->expires[$key] = microtime(true) + $ttl; + $this->expiresQueue->insert($key, 0 - $this->expires[$key]); + } + // unset before setting to ensure this entry will be added to end of array unset($this->data[$key]); $this->data[$key] = $value; - // ensure size limit is not exceeded or remove first entry from array - if ($this->limit !== null && count($this->data) > $this->limit) { - reset($this->data); - unset($this->data[key($this->data)]); - } + $this->garbageCollection(); return Promise\resolve(true); } public function remove($key) { - unset($this->data[$key]); + unset($this->data[$key], $this->expires[$key]); + $this->garbageCollection(); return Promise\resolve(true); } + + private function garbageCollection() + { + // ensure size limit is not exceeded or remove first entry from array + while ($this->limit !== null && count($this->data) > $this->limit) { + reset($this->data); + unset($this->data[key($this->data)]); + } + + if ($this->expiresQueue->count() === 0) { + return; + } + + $this->expiresQueue->rewind(); + do { + $run = false; + $item = $this->expiresQueue->current(); + if ((int)substr((string)$item['priority'], 1) <= microtime(true)) { + $this->expiresQueue->extract(); + $run = true; + unset($this->data[$item['data']], $this->expires[$item['data']]); + } + } while ($run && $this->expiresQueue->count() > 0); + } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 3832fac..36968e0 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -34,9 +34,10 @@ public function get($key, $default = null); * * @param string $key * @param mixed $value + * @param float|null $ttl * @return PromiseInterface Returns a promise which resolves to true on success of false on error */ - public function set($key, $value); + public function set($key, $value, $ttl = null); /** * Remove an item from the cache, returns a promise which resolves to true on success or diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index bacf448..4134711 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -152,4 +152,47 @@ public function testGetWithLimitedSizeWillUpdateLRUInfo() $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); $this->cache->get('baz')->then($this->expectCallableOnceWith('3')); } + + /** @test */ + public function getWithinTtl() + { + $this->cache + ->set('foo', 'bar', 100); + + + $success = $this->createCallableMock(); + $success + ->expects($this->once()) + ->method('__invoke') + ->with('bar'); + + $this->cache + ->get('foo') + ->then( + $success, + $this->expectCallableNever() + ); + } + + /** @test */ + public function getAfterTtl() + { + $this->cache + ->set('foo', 'bar', 1); + + sleep(2); + + $success = $this->createCallableMock(); + $success + ->expects($this->once()) + ->method('__invoke') + ->with(null); + + $this->cache + ->get('foo') + ->then( + $success, + $this->expectCallableNever() + ); + } } From 1879cb0f4b0d202e824fa2f3d3af38291fc902a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 23 May 2018 18:41:14 +0200 Subject: [PATCH 35/78] Use sorted array to store TTL and improve memory consumption --- README.md | 25 +++++++++----- src/ArrayCache.php | 70 ++++++++++++++++------------------------ src/CacheInterface.php | 28 +++++++++++++--- tests/ArrayCacheTest.php | 60 +++++++++++++++++----------------- 4 files changed, 96 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 67874f4..81808ab 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,13 @@ provide alternate implementations. #### get() -The `get(string $key, mixed $default = null): PromiseInterfae` method can be used to +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 @@ -53,20 +55,27 @@ 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). -If the key `foo` does not exist or when the TTL has passed, the promise will -be fulfilled with `null` as value. On any error it will also resolve with `null`. - #### 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. To provide guarantees as to when the cache -value is set a promise is returned. The promise will fulfill with `true` on success -or `false` on error. If the cache implementation has to go over the network to store -it, it may take a while. +already exists, it is overridden. #### remove() diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 83f3e34..8512a1f 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -3,7 +3,6 @@ namespace React\Cache; use React\Promise; -use SplPriorityQueue; class ArrayCache implements CacheInterface { @@ -11,11 +10,6 @@ class ArrayCache implements CacheInterface private $data = array(); private $expires = array(); - /** - * @var SplPriorityQueue - */ - private $expiresQueue; - /** * The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). * @@ -46,14 +40,13 @@ class ArrayCache implements CacheInterface public function __construct($limit = null) { $this->limit = $limit; - $this->expiresQueue = new SplPriorityQueue(); - $this->expiresQueue->setExtractFlags(SplPriorityQueue::EXTR_BOTH); } public function get($key, $default = null) { - if (array_key_exists($key, $this->expires)) { - $this->garbageCollection(); + // 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)) { @@ -64,23 +57,38 @@ public function get($key, $default = null) $value = $this->data[$key]; unset($this->data[$key]); $this->data[$key] = $value; + return Promise\resolve($value); } public function set($key, $value, $ttl = null) { - $expires = 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; - if (is_int($ttl)) { + // sort expiration times if TTL is given (first will expire first) + unset($this->expires[$key]); + if ($ttl !== null) { $this->expires[$key] = microtime(true) + $ttl; - $this->expiresQueue->insert($key, 0 - $this->expires[$key]); + asort($this->expires); } - // unset before setting to ensure this entry will be added to end of array - unset($this->data[$key]); - $this->data[$key] = $value; - - $this->garbageCollection(); + // 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); } @@ -88,31 +96,7 @@ public function set($key, $value, $ttl = null) public function remove($key) { unset($this->data[$key], $this->expires[$key]); - $this->garbageCollection(); - return Promise\resolve(true); - } - private function garbageCollection() - { - // ensure size limit is not exceeded or remove first entry from array - while ($this->limit !== null && count($this->data) > $this->limit) { - reset($this->data); - unset($this->data[key($this->data)]); - } - - if ($this->expiresQueue->count() === 0) { - return; - } - - $this->expiresQueue->rewind(); - do { - $run = false; - $item = $this->expiresQueue->current(); - if ((int)substr((string)$item['priority'], 1) <= microtime(true)) { - $this->expiresQueue->extract(); - $run = true; - unset($this->data[$item['data']], $this->expires[$item['data']]); - } - } while ($run && $this->expiresQueue->count() > 0); + return Promise\resolve(true); } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 36968e0..da46cae 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -11,6 +11,8 @@ interface CacheInterface * * 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 @@ -29,13 +31,29 @@ interface CacheInterface public function get($key, $default = null); /** - * Store an item in the cache, returns a promise which resolves to true on success or - * false on error. + * 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|null $ttl - * @return PromiseInterface Returns a promise which resolves to true on success of false on error + * @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); diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 4134711..a5a23df 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -153,46 +153,44 @@ public function testGetWithLimitedSizeWillUpdateLRUInfo() $this->cache->get('baz')->then($this->expectCallableOnceWith('3')); } - /** @test */ - public function getWithinTtl() + public function testGetWillResolveWithValueIfItemIsNotExpired() { - $this->cache - ->set('foo', 'bar', 100); + $this->cache = new ArrayCache(); + $this->cache->set('foo', '1', 10); - $success = $this->createCallableMock(); - $success - ->expects($this->once()) - ->method('__invoke') - ->with('bar'); + $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); + } - $this->cache - ->get('foo') - ->then( - $success, - $this->expectCallableNever() - ); + public function testGetWillResolveWithDefaultIfItemIsExpired() + { + $this->cache = new ArrayCache(); + + $this->cache->set('foo', '1', 0); + + $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - /** @test */ - public function getAfterTtl() + public function testSetWillOverwritOldestItemIfNoEntryIsExpired() { - $this->cache - ->set('foo', 'bar', 1); + $this->cache = new ArrayCache(2); - sleep(2); + $this->cache->set('foo', '1', 10); + $this->cache->set('bar', '2', 20); + $this->cache->set('baz', '3', 30); - $success = $this->createCallableMock(); - $success - ->expects($this->once()) - ->method('__invoke') - ->with(null); + $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); + } - $this->cache - ->get('foo') - ->then( - $success, - $this->expectCallableNever() - ); + 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)); } } From 9dad4b353fd03324df31bc190a3da738cd95e9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 8 Jun 2018 14:02:47 +0200 Subject: [PATCH 36/78] Rename remove() to delete() for consistency with PSR-16 --- README.md | 19 +++++++++++++------ src/ArrayCache.php | 2 +- src/CacheInterface.php | 21 ++++++++++++++++----- tests/ArrayCacheTest.php | 8 ++++---- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 81808ab..13bf887 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ provide alternate implementations. * [CacheInterface](#cacheinterface) * [get()](#get) * [set()](#set) - * [remove()](#remove) + * [delete()](#delete) * [ArrayCache](#arraycache) * [Common usage](#common-usage) * [Fallback get](#fallback-get) @@ -77,15 +77,22 @@ $cache->set('foo', 'bar', 60); This example eventually sets the value of the key `foo` to `bar`. If it already exists, it is overridden. -#### remove() +#### 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->remove('foo'); +$cache->delete('foo'); ``` -This example eventually removes 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. +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 diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 8512a1f..314814a 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -93,7 +93,7 @@ public function set($key, $value, $ttl = null) return Promise\resolve(true); } - public function remove($key) + public function delete($key) { unset($this->data[$key], $this->expires[$key]); diff --git a/src/CacheInterface.php b/src/CacheInterface.php index da46cae..f31a68e 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -58,12 +58,23 @@ public function get($key, $default = null); public function set($key, $value, $ttl = null); /** - * Remove an item from the cache, returns a promise which resolves to true on success or - * false on error. When the $key isn't found in the cache it also - * resolves true. + * 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 of false on error + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function remove($key); + public function delete($key); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index a5a23df..3336012 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -59,13 +59,13 @@ public function setShouldSetKey() } /** @test */ - public function removeShouldRemoveKey() + public function deleteShouldDeleteKey() { $this->cache ->set('foo', 'bar'); - $removePromise = $this->cache - ->remove('foo'); + $deletePromise = $this->cache + ->delete('foo'); $mock = $this->createCallableMock(); $mock @@ -73,7 +73,7 @@ public function removeShouldRemoveKey() ->method('__invoke') ->with($this->identicalTo(true)); - $removePromise->then($mock); + $deletePromise->then($mock); $this->cache ->get('foo') From 7d7da7fb7574d471904ba357b39bbf110ccdbf66 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 25 Jun 2018 14:52:40 +0200 Subject: [PATCH 37/78] Prepare v0.5.0 release --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d1801..c2df5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # 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 diff --git a/README.md b/README.md index 13bf887..9e7ba2b 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/cache:^0.4.2 +$ composer require react/cache:^0.5.0 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 8f501c66b9fe73e215fffaeca3e6c4ae0dc2e2da Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Wed, 3 Oct 2018 20:07:39 +0200 Subject: [PATCH 38/78] Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function --- src/ArrayCache.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 314814a..2b4e1c1 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -45,11 +45,11 @@ public function __construct($limit = null) 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)) { + if (isset($this->expires[$key]) && $this->expires[$key] < \microtime(true)) { unset($this->data[$key], $this->expires[$key]); } - if (!array_key_exists($key, $this->data)) { + if (!\array_key_exists($key, $this->data)) { return Promise\resolve($default); } @@ -70,22 +70,22 @@ public function set($key, $value, $ttl = null) // 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); + $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) { + 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); + \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); + if ($key === null || $this->expires[$key] > \microtime(true)) { + \reset($this->data); + $key = \key($this->data); } unset($this->data[$key], $this->expires[$key]); } From 03aad31499a0ade7ef11c4e0f28c5f39ff6a18d6 Mon Sep 17 00:00:00 2001 From: krlv Date: Thu, 25 Oct 2018 21:03:53 -0700 Subject: [PATCH 39/78] Add PSR-16 methods support: getMultiple and setMultiple --- README.md | 43 ++++++++++++++++++++++++++++++++++ src/ArrayCache.php | 21 +++++++++++++++++ src/CacheInterface.php | 50 ++++++++++++++++++++++++++++++++++++++++ tests/ArrayCacheTest.php | 20 ++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/README.md b/README.md index 9e7ba2b..6843a12 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ provide alternate implementations. * [get()](#get) * [set()](#set) * [delete()](#delete) + * [getMultiple](#getmultiple) + * [setMultiple](#setmultiple) * [ArrayCache](#arraycache) * [Common usage](#common-usage) * [Fallback get](#fallback-get) @@ -94,6 +96,47 @@ 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. +#### getMultiple() + +The `getMultiple(iterable $keys, mixed $default = null): PromiseInterface` method can be used to +retrieves multiple cache items by their unique keys. + +This method will resolve with the list of 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 + ->getMultiple(array('foo', 'bar')) + ->then('var_dump'); +``` + +This example fetches the list of value for `foo` and `bar` keys and passes it to the +`var_dump` function. You can use any of the composition provided by +[promises](https://github.com/reactphp/promise). + +#### setMultiple() + +Persists a set of key => value pairs in the cache, with an optional TTL. + +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 these cache items. If this parameter is omitted (or `null`), these items +will stay in the cache for as long as the underlying implementation +supports. Trying to access an expired cache items results in a cache miss, +see also [`getMultiple()`](#getmultiple). + +```php +$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); +``` + +This example eventually sets the list of values - the key `foo` to `1` value +and the key `bar` to `2`. If some of the keys already exist, they are overridden. + ### ArrayCache The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 2b4e1c1..7b5a08e 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -3,6 +3,7 @@ namespace React\Cache; use React\Promise; +use React\Promise\PromiseInterface; class ArrayCache implements CacheInterface { @@ -99,4 +100,24 @@ public function delete($key) return Promise\resolve(true); } + + public function getMultiple($keys, $default = null) + { + $values = array(); + + foreach ($keys as $key) { + $values[$key] = $this->get($key, $default); + } + + return Promise\all($values); + } + + public function setMultiple($values, $ttl = null) + { + foreach ($values as $key => $value) { + $this->set($key, $value, $ttl); + } + + return Promise\resolve(true); + } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index f31a68e..42aca90 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -77,4 +77,54 @@ public function set($key, $value, $ttl = null); * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function delete($key); + + /** + * Retrieves multiple cache items by their unique keys. + * + * This method will resolve with the list of 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 + * ->getMultiple(array('foo', 'bar')) + * ->then('var_dump'); + * ``` + * + * This example fetches the list of value for `foo` and `bar` keys and passes it to the + * `var_dump` function. You can use any of the composition provided by + * [promises](https://github.com/reactphp/promise). + * + * @param iterable $keys A list of keys that can obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * @return PromiseInterface + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * 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 these cache items. If this parameter is omitted (or `null`), these items + * will stay in the cache for as long as the underlying implementation + * supports. Trying to access an expired cache items results in a cache miss, + * see also [`get()`](#get). + * + * ```php + * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); + * ``` + * + * This example eventually sets the list of values - the key `foo` to 1 value + * and the key `bar` to 2. If some of the keys already exist, they are overridden. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param ?float $ttl Optional. The TTL value of this item. + * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function setMultiple($values, $ttl = null); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 3336012..939424e 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -193,4 +193,24 @@ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired() $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); } + + public function testGetMultiple() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', '1'); + + $this->cache + ->getMultiple(array('foo', 'bar'), 'baz') + ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz'))); + } + + public function testSetMultiple() + { + $this->cache = new ArrayCache(); + $this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10); + + $this->cache + ->getMultiple(array('foo', 'bar')) + ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2'))); + } } From 867279e5b568c9f4606a6f96216df4efe0664267 Mon Sep 17 00:00:00 2001 From: krlv Date: Tue, 30 Oct 2018 00:41:39 -0700 Subject: [PATCH 40/78] Add PSR-16 methods: deleteMultiple, clear, has --- README.md | 65 +++++++++++++++++++++++- src/ArrayCache.php | 36 +++++++++++++ src/CacheInterface.php | 28 +++++++++++ tests/ArrayCacheTest.php | 106 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6843a12..749564e 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,11 @@ provide alternate implementations. * [get()](#get) * [set()](#set) * [delete()](#delete) - * [getMultiple](#getmultiple) - * [setMultiple](#setmultiple) + * [getMultiple()](#getmultiple) + * [setMultiple()](#setmultiple) + * [deleteMultiple()](#deletemultiple) + * [clear()](#clear) + * [has()](#has) * [ArrayCache](#arraycache) * [Common usage](#common-usage) * [Fallback get](#fallback-get) @@ -137,6 +140,64 @@ $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); This example eventually sets the list of values - the key `foo` to `1` value and the key `bar` to `2`. If some of the keys already exist, they are overridden. +#### deleteMultiple() + +Deletes multiple cache items in a single operation. + +This method will resolve with `true` on success or `false` when an error +occurs. When no items for `$keys` are 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->deleteMultiple(array('foo', 'bar, 'baz')); +``` + +This example eventually deletes keys `foo`, `bar` and `baz` from the cache. +As with `setMultiple()`, this may not happen instantly and a promise is returned to +provide guarantees whether or not the item has been removed from cache. + +#### clear() + +Wipes clean the entire 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 +delete it, it may take a while. + +```php +$cache->clear(); +``` + +This example eventually deletes all keys from the cache. As with `deleteMultiple()`, +this may not happen instantly and a promise is returned to provide guarantees +whether or not all the items have been removed from cache. + +#### has() + +Determines whether an item is present in the cache. + +This method will resolve with `true` on success or reject with `false` +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 + ->has('foo') + ->then('var_dump') + ->otherwise('error_log'); +``` + +This example checks if the value of the key `foo` is set in the cache. If it is set, +`true` will be passed to the `var_dump` function; otherwise, `false` will be passed +to the `error_log` function. You can use any of the composition provided by +[promises](https://github.com/reactphp/promise). + +NOTE: It is recommended that has() is only to be used for cache warming type purposes +and not to be used within your live applications operations for get/set, as this method +is subject to a race condition where your has() will return true and immediately after, +another script can remove it making the state of your app out of date. + ### ArrayCache The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 7b5a08e..a31c3ea 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -120,4 +120,40 @@ public function setMultiple($values, $ttl = null) return Promise\resolve(true); } + + public function deleteMultiple($keys) + { + foreach ($keys as $key) { + unset($this->data[$key], $this->expires[$key]); + } + + return Promise\resolve(true); + } + + public function clear() + { + $this->data = array(); + $this->expires = array(); + + return Promise\resolve(true); + } + + public function has($key) + { + // delete key if it is already expired + 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\reject(false); + } + + // 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(true); + } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 42aca90..3309217 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -127,4 +127,32 @@ public function getMultiple($keys, $default = null); * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function deleteMultiple($keys); + + /** + * Wipes clean the entire cache. + * + * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + */ + public function clear(); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * @return PromiseInterface Returns a promise which resolves to `true` on success or rejects `` on error + */ + public function has($key); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 939424e..0e5c262 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -213,4 +213,110 @@ public function testSetMultiple() ->getMultiple(array('foo', 'bar')) ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2'))); } + + public function testDeleteMultiple() + { + $this->cache = new ArrayCache(); + $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3)); + + $this->cache + ->deleteMultiple(array('foo', 'baz')) + ->then($this->expectCallableOnceWith(true)); + + $this->cache + ->has('foo') + ->otherwise($this->expectCallableOnceWith(false)); + + $this->cache + ->has('bar') + ->then($this->expectCallableOnceWith(true)); + + $this->cache + ->has('baz') + ->otherwise($this->expectCallableOnceWith(false)); + } + + public function testClearShouldClearCache() + { + $this->cache = new ArrayCache(); + $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3)); + + $this->cache->clear(); + + $this->cache + ->has('foo') + ->otherwise($this->expectCallableOnceWith(false)); + + $this->cache + ->has('bar') + ->otherwise($this->expectCallableOnceWith(false)); + + $this->cache + ->has('baz') + ->otherwise($this->expectCallableOnceWith(false)); + } + + public function hasShouldResolvePromiseForExistingKey() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', 'bar'); + + $this->cache + ->has('foo') + ->then($this->expectCallableOnceWith(true)); + } + + public function hasShouldRejectPromiseForNonExistentKey() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', 'bar'); + + $this->cache + ->has('foo') + ->otherwise($this->expectCallableOnceWith(false)); + } + + public function testHasWillResolveIfItemIsNotExpired() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', '1', 10); + + $this->cache + ->has('foo') + ->then($this->expectCallableOnceWith(true)); + } + + public function testHasWillRejectIfItemIsExpired() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', '1', 0); + + $this->cache + ->has('foo') + ->otherwise($this->expectCallableOnceWith(false)); + } + + public function testHasWillResolveForExplicitNullValue() + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', null); + + $this->cache + ->has('foo') + ->then($this->expectCallableOnceWith(true)); + } + + public function testHasWithLimitedSizeWillUpdateLRUInfo() + { + $this->cache = new ArrayCache(2); + + $this->cache->set('foo', 1); + $this->cache->set('bar', 2); + $this->cache->has('foo')->then($this->expectCallableOnceWith(true)); + $this->cache->set('baz', 3); + + $this->cache->has('foo')->then($this->expectCallableOnceWith(1)); + $this->cache->has('bar')->otherwise($this->expectCallableOnceWith(false)); + $this->cache->has('baz')->then($this->expectCallableOnceWith(3)); + } } From 1ad88361a630aa727e31aff999d216bdf59178df Mon Sep 17 00:00:00 2001 From: krlv Date: Tue, 30 Oct 2018 13:22:00 -0700 Subject: [PATCH 41/78] PSR-16 method has: resolve with false instead of reject on a cache miss --- README.md | 14 ++++++-------- src/ArrayCache.php | 2 +- src/CacheInterface.php | 16 +++++++++++++++- tests/ArrayCacheTest.php | 20 ++++++++++---------- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 749564e..9da2703 100644 --- a/README.md +++ b/README.md @@ -177,20 +177,18 @@ whether or not all the items have been removed from cache. Determines whether an item is present in the cache. -This method will resolve with `true` on success or reject with `false` -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. +This method will resolve with `true` on success or `false` 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 ->has('foo') - ->then('var_dump') - ->otherwise('error_log'); + ->then('var_dump'); ``` -This example checks if the value of the key `foo` is set in the cache. If it is set, -`true` will be passed to the `var_dump` function; otherwise, `false` will be passed -to the `error_log` function. You can use any of the composition provided by +This example checks if the value of the key `foo` is set in the cache and passes +the result to the `var_dump` function. You can use any of the composition provided by [promises](https://github.com/reactphp/promise). NOTE: It is recommended that has() is only to be used for cache warming type purposes diff --git a/src/ArrayCache.php b/src/ArrayCache.php index a31c3ea..dd940af 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -146,7 +146,7 @@ public function has($key) } if (!\array_key_exists($key, $this->data)) { - return Promise\reject(false); + return Promise\resolve(false); } // remove and append to end of array to keep track of LRU info diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 3309217..d3628ed 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -146,13 +146,27 @@ public function clear(); /** * Determines whether an item is present in the cache. * + * This method will resolve with `true` on success or `false` 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 + * ->has('foo') + * ->then('var_dump'); + * ``` + * + * This example checks if the value of the key `foo` is set in the cache and passes + * the result to the `var_dump` function. You can use any of the composition provided by + * [promises](https://github.com/reactphp/promise). + * * NOTE: It is recommended that has() is only to be used for cache warming type purposes * and not to be used within your live applications operations for get/set, as this method * is subject to a race condition where your has() will return true and immediately after, * another script can remove it making the state of your app out of date. * * @param string $key The cache item key. - * @return PromiseInterface Returns a promise which resolves to `true` on success or rejects `` on error + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function has($key); } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 0e5c262..3b5bd8c 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -225,7 +225,7 @@ public function testDeleteMultiple() $this->cache ->has('foo') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); $this->cache ->has('bar') @@ -233,7 +233,7 @@ public function testDeleteMultiple() $this->cache ->has('baz') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); } public function testClearShouldClearCache() @@ -245,15 +245,15 @@ public function testClearShouldClearCache() $this->cache ->has('foo') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); $this->cache ->has('bar') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); $this->cache ->has('baz') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); } public function hasShouldResolvePromiseForExistingKey() @@ -266,14 +266,14 @@ public function hasShouldResolvePromiseForExistingKey() ->then($this->expectCallableOnceWith(true)); } - public function hasShouldRejectPromiseForNonExistentKey() + public function hasShouldResolvePromiseForNonExistentKey() { $this->cache = new ArrayCache(); $this->cache->set('foo', 'bar'); $this->cache ->has('foo') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); } public function testHasWillResolveIfItemIsNotExpired() @@ -286,14 +286,14 @@ public function testHasWillResolveIfItemIsNotExpired() ->then($this->expectCallableOnceWith(true)); } - public function testHasWillRejectIfItemIsExpired() + public function testHasWillResolveIfItemIsExpired() { $this->cache = new ArrayCache(); $this->cache->set('foo', '1', 0); $this->cache ->has('foo') - ->otherwise($this->expectCallableOnceWith(false)); + ->then($this->expectCallableOnceWith(false)); } public function testHasWillResolveForExplicitNullValue() @@ -316,7 +316,7 @@ public function testHasWithLimitedSizeWillUpdateLRUInfo() $this->cache->set('baz', 3); $this->cache->has('foo')->then($this->expectCallableOnceWith(1)); - $this->cache->has('bar')->otherwise($this->expectCallableOnceWith(false)); + $this->cache->has('bar')->then($this->expectCallableOnceWith(false)); $this->cache->has('baz')->then($this->expectCallableOnceWith(3)); } } From 8762fe5b49e435c63ed376339a874c606bd17f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 12 Jan 2019 14:57:59 +0100 Subject: [PATCH 42/78] Improve documentation --- README.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9da2703..b93c1da 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Cache Component +# Cache -[![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) +[![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). @@ -11,6 +11,9 @@ The cache component provides a implementation of that. This allows consumers to type hint against the interface and third parties to provide alternate implementations. +This project is heavily inspired by +[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/), +but uses an interface more suited for async, non-blocking applications. **Table of Contents** @@ -42,7 +45,7 @@ provide alternate implementations. #### get() -The `get(string $key, mixed $default = null): PromiseInterface` method can be used to +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 @@ -62,7 +65,7 @@ This example fetches the value of the key `foo` and passes it to the #### set() -The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to +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 @@ -84,7 +87,8 @@ already exists, it is overridden. #### delete() -Deletes an item from the cache. +The `delete(string $key): PromiseInterface` method can be used to +delete 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 @@ -101,8 +105,8 @@ provide guarantees whether or not the item has been removed from cache. #### getMultiple() -The `getMultiple(iterable $keys, mixed $default = null): PromiseInterface` method can be used to -retrieves multiple cache items by their unique keys. +The `getMultiple(iterable $keys, mixed $default = null): PromiseInterface` method can be used to +retrieve multiple cache items by their unique keys. This method will resolve with the list of cached value on success or with the given `$default` value when no item can be found or when an error occurs. @@ -121,7 +125,8 @@ This example fetches the list of value for `foo` and `bar` keys and passes it to #### setMultiple() -Persists a set of key => value pairs in the cache, with an optional TTL. +The `setMultiple(iterable $values, ?float $ttl = null): PromiseInterface` method can be used to +persist a set of key => value pairs in the cache, with an optional TTL. 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 @@ -142,7 +147,8 @@ and the key `bar` to `2`. If some of the keys already exist, they are overridden #### deleteMultiple() -Deletes multiple cache items in a single operation. +The `setMultiple(iterable $keys): PromiseInterface` method can be used to +delete multiple cache items in a single operation. This method will resolve with `true` on success or `false` when an error occurs. When no items for `$keys` are found in the cache, it also resolves @@ -159,7 +165,8 @@ provide guarantees whether or not the item has been removed from cache. #### clear() -Wipes clean the entire cache. +The `clear(): PromiseInterface` method can be used to +wipe clean the entire 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 @@ -175,7 +182,8 @@ whether or not all the items have been removed from cache. #### has() -Determines whether an item is present in the cache. +The `has(string $key): PromiseInterface` method can be used to +determine whether an item is present in the cache. This method will resolve with `true` on success or `false` when no item can be found or when an error occurs. Similarly, an expired cache item (once the time-to-live From c94cde9a49f174d5de1e101db0abc182767d23d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 27 Apr 2019 12:54:44 +0200 Subject: [PATCH 43/78] Allow legacy HHVM to fail in Travis CI config HHVM decided to drop support for PHP, so it's questionable if supporting it in the future is worth the effort. https://hhvm.com/blog/2019/02/11/hhvm-4.0.0.html This PR does not aim to discuss how useful HHVM is and whether or not we want to continue supporting it. It only aims to make the test output consistent by ignoring legacy HHVM fails in the build matrix to not mark the complete test suite as failed. Refs https://github.com/clue/php-socket-raw/pull/42 and others. --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 290df75..36ad686 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ php: - 5.5 - 5.6 - 7 - - hhvm +# - hhvm # requires legacy phpunit & ignore errors, see below # lock distro so new future defaults will not break the build dist: trusty @@ -15,6 +15,10 @@ matrix: include: - php: 5.3 dist: precise + - php: hhvm + install: composer require phpunit/phpunit:^5 --dev --no-interaction + allow_failures: + - php: hhvm sudo: false From 47a54388bef7ec3d941b402938758689eb2e7417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 6 Apr 2019 11:56:56 +0200 Subject: [PATCH 44/78] Use high-resolution timer for cache TTL on PHP 7.3+ --- .travis.yml | 7 +++++-- README.md | 21 +++++++++++++++++++++ src/ArrayCache.php | 30 ++++++++++++++++++++++++++---- src/CacheInterface.php | 11 +++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36ad686..402a996 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,10 @@ php: - 5.4 - 5.5 - 5.6 - - 7 + - 7.0 + - 7.1 + - 7.2 + - 7.3 # - hhvm # requires legacy phpunit & ignore errors, see below # lock distro so new future defaults will not break the build @@ -24,6 +27,6 @@ sudo: false install: - composer install --no-interaction - + script: - ./vendor/bin/phpunit --coverage-text diff --git a/README.md b/README.md index b93c1da..3feef7f 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,17 @@ $cache->set('foo', 'bar', 60); This example eventually sets the value of the key `foo` to `bar`. If it already exists, it is overridden. +This interface suggests that cache implementations SHOULD use a monotonic +time source if available. Given that a monotonic time source is only +available as of PHP 7.3 by default, cache implementations MAY fall back +to using wall-clock time. +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you store a cache item with a TTL of 30s and then +adjust your system time forward by 20s, the cache item SHOULD still +expire in 30s. + #### delete() The `delete(string $key): PromiseInterface` method can be used to @@ -230,6 +241,16 @@ $cache->set('bar', '2'); $cache->set('baz', '3'); ``` +This cache implementation is known to rely on wall-clock time to schedule +future cache expiration times when using any version before PHP 7.3, +because a monotonic time source is only available as of PHP 7.3 (`hrtime()`). +While this does not affect many common use cases, this is an important +distinction for programs that rely on a high time precision or on systems +that are subject to discontinuous time adjustments (time jumps). +This means that if you store a cache item with a TTL of 30s on PHP < 7.3 +and then adjust your system time forward by 20s, the cache item may +expire in 10s. See also [`set()`](#set) for more details. + ## Common usage ### Fallback get diff --git a/src/ArrayCache.php b/src/ArrayCache.php index dd940af..e3c14e5 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -10,6 +10,7 @@ class ArrayCache implements CacheInterface private $limit; private $data = array(); private $expires = array(); + private $supportsHighResolution; /** * The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface). @@ -36,17 +37,30 @@ class ArrayCache implements CacheInterface * $cache->set('baz', '3'); * ``` * + * This cache implementation is known to rely on wall-clock time to schedule + * future cache expiration times when using any version before PHP 7.3, + * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`). + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you store a cache item with a TTL of 30s on PHP < 7.3 + * and then adjust your system time forward by 20s, the cache item may + * expire in 10s. See also [`set()`](#set) for more details. + * * @param int|null $limit maximum number of entries to store in the LRU cache */ public function __construct($limit = null) { $this->limit = $limit; + + // prefer high-resolution timer, available as of PHP 7.3+ + $this->supportsHighResolution = \function_exists('hrtime'); } 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)) { + if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { unset($this->data[$key], $this->expires[$key]); } @@ -71,7 +85,7 @@ public function set($key, $value, $ttl = null) // sort expiration times if TTL is given (first will expire first) unset($this->expires[$key]); if ($ttl !== null) { - $this->expires[$key] = \microtime(true) + $ttl; + $this->expires[$key] = $this->now() + $ttl; \asort($this->expires); } @@ -84,7 +98,7 @@ public function set($key, $value, $ttl = null) // 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)) { + if ($key === null || $this->now() - $this->expires[$key] < 0) { \reset($this->data); $key = \key($this->data); } @@ -141,7 +155,7 @@ public function clear() public function has($key) { // delete key if it is already expired - if (isset($this->expires[$key]) && $this->expires[$key] < \microtime(true)) { + if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { unset($this->data[$key], $this->expires[$key]); } @@ -156,4 +170,12 @@ public function has($key) return Promise\resolve(true); } + + /** + * @return float + */ + private function now() + { + return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); + } } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index d3628ed..76c3a6c 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -50,6 +50,17 @@ public function get($key, $default = null); * This example eventually sets the value of the key `foo` to `bar`. If it * already exists, it is overridden. * + * This interface suggests that cache implementations SHOULD use a monotonic + * time source if available. Given that a monotonic time source is only + * available as of PHP 7.3 by default, cache implementations MAY fall back + * to using wall-clock time. + * While this does not affect many common use cases, this is an important + * distinction for programs that rely on a high time precision or on systems + * that are subject to discontinuous time adjustments (time jumps). + * This means that if you store a cache item with a TTL of 30s and then + * adjust your system time forward by 20s, the cache item SHOULD still + * expire in 30s. + * * @param string $key * @param mixed $value * @param ?float $ttl From b60b54aa20059cd73b140d20c3174d05aea5c5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 13 Feb 2019 18:33:30 +0100 Subject: [PATCH 45/78] Use arrays instead of iterable for multiple cache items Using arrays makes the API slightly stricter and allows consumers to consistently rely on associative arrays for getMultiple() and setMultiple(). --- README.md | 25 ++++++++++++----------- src/ArrayCache.php | 6 +++--- src/CacheInterface.php | 45 ++++++++++++++++++++++-------------------- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3feef7f..11ece86 100644 --- a/README.md +++ b/README.md @@ -116,27 +116,30 @@ provide guarantees whether or not the item has been removed from cache. #### getMultiple() -The `getMultiple(iterable $keys, mixed $default = null): PromiseInterface` method can be used to +The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to retrieve multiple cache items by their unique keys. -This method will resolve with the list of cached value on success or with the -given `$default` value when no item can be found or when an error occurs. +This method will resolve with an array of cached values on success or with the +given `$default` value when an item can not 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 - ->getMultiple(array('foo', 'bar')) - ->then('var_dump'); +$cache->getMultiple(array('name', 'age'))->then(function (array $values) { + $name = $values['name'] ?? 'User'; + $age = $values['age'] ?? 'n/a'; + + echo $name . ' is ' . $age . PHP_EOL; +}); ``` -This example fetches the list of value for `foo` and `bar` keys and passes it to the -`var_dump` function. You can use any of the composition provided by -[promises](https://github.com/reactphp/promise). +This example fetches the cache items for the `name` and `age` keys and +prints some example output. You can use any of the composition provided +by [promises](https://github.com/reactphp/promise). #### setMultiple() -The `setMultiple(iterable $values, ?float $ttl = null): PromiseInterface` method can be used to +The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to persist a set of key => value pairs in the cache, with an optional TTL. This method will resolve with `true` on success or `false` when an error @@ -158,7 +161,7 @@ and the key `bar` to `2`. If some of the keys already exist, they are overridden #### deleteMultiple() -The `setMultiple(iterable $keys): PromiseInterface` method can be used to +The `setMultiple(string[] $keys): PromiseInterface` method can be used to delete multiple cache items in a single operation. This method will resolve with `true` on success or `false` when an error diff --git a/src/ArrayCache.php b/src/ArrayCache.php index e3c14e5..81f25ef 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -115,7 +115,7 @@ public function delete($key) return Promise\resolve(true); } - public function getMultiple($keys, $default = null) + public function getMultiple(array $keys, $default = null) { $values = array(); @@ -126,7 +126,7 @@ public function getMultiple($keys, $default = null) return Promise\all($values); } - public function setMultiple($values, $ttl = null) + public function setMultiple(array $values, $ttl = null) { foreach ($values as $key => $value) { $this->set($key, $value, $ttl); @@ -135,7 +135,7 @@ public function setMultiple($values, $ttl = null) return Promise\resolve(true); } - public function deleteMultiple($keys) + public function deleteMultiple(array $keys) { foreach ($keys as $key) { unset($this->data[$key], $this->expires[$key]); diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 76c3a6c..424149c 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -92,26 +92,29 @@ public function delete($key); /** * Retrieves multiple cache items by their unique keys. * - * This method will resolve with the list of cached value on success or with the - * given `$default` value when no item can be found or when an error occurs. + * This method will resolve with an array of cached values on success or with the + * given `$default` value when an item can not 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 - * ->getMultiple(array('foo', 'bar')) - * ->then('var_dump'); + * $cache->getMultiple(array('name', 'age'))->then(function (array $values) { + * $name = $values['name'] ?? 'User'; + * $age = $values['age'] ?? 'n/a'; + * + * echo $name . ' is ' . $age . PHP_EOL; + * }); * ``` * - * This example fetches the list of value for `foo` and `bar` keys and passes it to the - * `var_dump` function. You can use any of the composition provided by - * [promises](https://github.com/reactphp/promise). + * This example fetches the cache items for the `name` and `age` keys and + * prints some example output. You can use any of the composition provided + * by [promises](https://github.com/reactphp/promise). * - * @param iterable $keys A list of keys that can obtained in a single operation. - * @param mixed $default Default value to return for keys that do not exist. - * @return PromiseInterface + * @param string[] $keys A list of keys that can obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * @return PromiseInterface Returns a promise which resolves to an `array` of cached values */ - public function getMultiple($keys, $default = null); + public function getMultiple(array $keys, $default = null); /** * Persists a set of key => value pairs in the cache, with an optional TTL. @@ -133,24 +136,24 @@ public function getMultiple($keys, $default = null); * This example eventually sets the list of values - the key `foo` to 1 value * and the key `bar` to 2. If some of the keys already exist, they are overridden. * - * @param iterable $values A list of key => value pairs for a multiple-set operation. - * @param ?float $ttl Optional. The TTL value of this item. - * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + * @param array $values A list of key => value pairs for a multiple-set operation. + * @param ?float $ttl Optional. The TTL value of this item. + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function setMultiple($values, $ttl = null); + public function setMultiple(array $values, $ttl = null); /** * Deletes multiple cache items in a single operation. * - * @param iterable $keys A list of string-based keys to be deleted. - * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + * @param string[] $keys A list of string-based keys to be deleted. + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function deleteMultiple($keys); + public function deleteMultiple(array $keys); /** * Wipes clean the entire cache. * - * @return bool PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function clear(); @@ -177,7 +180,7 @@ public function clear(); * another script can remove it making the state of your app out of date. * * @param string $key The cache item key. - * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function has($key); } From 7bc8ba6343297d7cf91867a10f71c3611cf5b9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Jun 2019 21:40:13 +0200 Subject: [PATCH 46/78] Documentation for TTL precision with millisecond accuracy or below --- README.md | 8 ++++++++ src/CacheInterface.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 3feef7f..311d829 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,14 @@ $cache->set('foo', 'bar', 60); This example eventually sets the value of the key `foo` to `bar`. If it already exists, it is overridden. +This interface does not enforce any particular TTL resolution, so special +care may have to be taken if you rely on very high precision with +millisecond accuracy or below. Cache implementations SHOULD work on a +best effort basis and SHOULD provide at least second accuracy unless +otherwise noted. Many existing cache implementations are known to provide +microsecond or millisecond accuracy, but it's generally not recommended +to rely on this high precision. + This interface suggests that cache implementations SHOULD use a monotonic time source if available. Given that a monotonic time source is only available as of PHP 7.3 by default, cache implementations MAY fall back diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 76c3a6c..b51bf1b 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -50,6 +50,14 @@ public function get($key, $default = null); * This example eventually sets the value of the key `foo` to `bar`. If it * already exists, it is overridden. * + * This interface does not enforce any particular TTL resolution, so special + * care may have to be taken if you rely on very high precision with + * millisecond accuracy or below. Cache implementations SHOULD work on a + * best effort basis and SHOULD provide at least second accuracy unless + * otherwise noted. Many existing cache implementations are known to provide + * microsecond or millisecond accuracy, but it's generally not recommended + * to rely on this high precision. + * * This interface suggests that cache implementations SHOULD use a monotonic * time source if available. Given that a monotonic time source is only * available as of PHP 7.3 by default, cache implementations MAY fall back From a42d149e15b8312918327d5be2ba757cd2f23ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 4 Jul 2019 16:45:59 +0200 Subject: [PATCH 47/78] Prepare v0.6.0 release --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2df5cc..75fb0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.6.0 (2019-07-04) + +* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()` + supporting multiple cache items (inspired by PSR-16). + (#32 by @krlv and #37 by @clue) + +* Documentation for TTL precision with millisecond accuracy or below and + use high-resolution timer for cache TTL on PHP 7.3+. + (#35 and #38 by @clue) + +* Improve API documentation and allow legacy HHVM to fail in Travis CI config. + (#34 and #36 by @clue) + +* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function. + (#31 by @WyriHaximus) + ## 0.5.0 (2018-06-25) * Improve documentation by describing what is expected of a class implementing `CacheInterface`. diff --git a/README.md b/README.md index 907597b..6e5cc46 100644 --- a/README.md +++ b/README.md @@ -335,7 +335,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/cache:^0.5.0 +$ composer require react/cache:^0.6 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From aa10d63a1b40a36a486bdf527f28bac607ee6466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 11 Jul 2019 15:45:28 +0200 Subject: [PATCH 48/78] Prepare v1.0.0 release --- CHANGELOG.md | 9 +++++++++ README.md | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75fb0ca..99ecd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 1.0.0 (2019-07-11) + +* First stable LTS release, now following [SemVer](https://semver.org/). + We'd like to emphasize that this component is production ready and battle-tested. + We plan to support all long-term support (LTS) releases for at least 24 months, + so you have a rock-solid foundation to build on top of. + +> Contains no other changes, so it's actually fully compatible with the v0.6.0 release. + ## 0.6.0 (2019-07-04) * Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()` diff --git a/README.md b/README.md index 6e5cc46..74cef54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cache -[![Build Status](https://secure.travis-ci.org/reactphp/cache.png?branch=master)](http://travis-ci.org/reactphp/cache) +[![Build Status](https://travis-ci.org/reactphp/cache.svg?branch=master)](https://travis-ci.org/reactphp/cache) Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). @@ -332,10 +332,11 @@ fetched from the database. The recommended way to install this library is [through Composer](https://getcomposer.org). [New to Composer?](https://getcomposer.org/doc/00-intro.md) +This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/cache:^0.6 +$ composer require react/cache:^1.0 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 41b9f09d4124d04ed04c419b5c291bba753e690c Mon Sep 17 00:00:00 2001 From: Sam Reed Date: Sun, 1 Dec 2019 01:59:57 +0000 Subject: [PATCH 49/78] Add .gitattributes to exclude dev files from exports --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..982c460 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore From 1dba66344084738d0667e0225c1b53cb128b1b8c Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 8 Oct 2019 18:10:25 +0200 Subject: [PATCH 50/78] Forward compatibility with react/promise 3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 51573b6..a8f4a43 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "require": { "php": ">=5.3.0", - "react/promise": "~2.0|~1.1" + "react/promise": "^3.0 || ^2.0 || ^1.1" }, "autoload": { "psr-4": { "React\\Cache\\": "src/" } From 1a0585326384ab2270d56778ea853f76a70a49ef Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 10:46:19 +0200 Subject: [PATCH 51/78] Run tests on PHPUnit 9 --- composer.json | 2 +- tests/ArrayCacheTest.php | 5 ++++- tests/CallableStub.php | 10 ---------- tests/TestCase.php | 8 +++++++- 4 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 tests/CallableStub.php diff --git a/composer.json b/composer.json index a8f4a43..ae9c39f 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,6 @@ "psr-4": { "React\\Tests\\Cache\\": "tests/" } }, "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" } } diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 3b5bd8c..420d45f 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -11,7 +11,10 @@ class ArrayCacheTest extends TestCase */ private $cache; - public function setUp() + /** + * @before + */ + public function setUpArrayCache() { $this->cache = new ArrayCache(); } diff --git a/tests/CallableStub.php b/tests/CallableStub.php deleted file mode 100644 index 2f547cd..0000000 --- a/tests/CallableStub.php +++ /dev/null @@ -1,10 +0,0 @@ -getMockBuilder('React\Tests\Cache\CallableStub')->getMock(); + if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + // PHPUnit 9+ + return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + } else { + // legacy PHPUnit 4 - PHPUnit 9 + return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + } } } From c393d4c06f399870af27f3bbc793c64dd5a6ed45 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 10:55:41 +0200 Subject: [PATCH 52/78] Run tests on PHP 7.4 and simplify test matrix --- .travis.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 402a996..0a5430e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,5 @@ language: php -php: -# - 5.3 # requires old distro, see below - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - 7.3 -# - hhvm # requires legacy phpunit & ignore errors, see below - # lock distro so new future defaults will not break the build dist: trusty @@ -18,10 +7,17 @@ matrix: include: - php: 5.3 dist: precise - - php: hhvm - install: composer require phpunit/phpunit:^5 --dev --no-interaction + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: hhvm-3.18 allow_failures: - - php: hhvm + - php: hhvm-3.18 sudo: false @@ -29,4 +25,4 @@ install: - composer install --no-interaction script: - - ./vendor/bin/phpunit --coverage-text + - vendor/bin/phpunit --coverage-text From 49bbd0c4c796426d0acc90f5ce655e3f31cd7d94 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 17 Jul 2020 10:59:14 +0200 Subject: [PATCH 53/78] Clean up test suite --- .travis.yml | 4 +--- phpunit.xml.dist | 7 +------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a5430e..1c59030 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php # lock distro so new future defaults will not break the build dist: trusty -matrix: +jobs: include: - php: 5.3 dist: precise @@ -19,8 +19,6 @@ matrix: allow_failures: - php: hhvm-3.18 -sudo: false - install: - composer install --no-interaction diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d02182f..0e947b8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,11 +1,6 @@ - + ./tests/ From 6c0dad809052f8ac77fb097cb40b2c939c9e3b68 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 18:54:25 +0200 Subject: [PATCH 54/78] Add full core team to the license Added the full core team in order of joining the team --- LICENSE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a808108..d6f8901 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2012 Igor Wiedler, Chris Boden +The MIT License (MIT) + +Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 48d5d8f7747303b5316984f0fc49450c4b047c29 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 24 Aug 2020 19:47:38 +0200 Subject: [PATCH 55/78] Add full core team to composer authors list --- composer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/composer.json b/composer.json index ae9c39f..480b1ec 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,28 @@ "description": "Async, Promise-based cache interface for ReactPHP", "keywords": ["cache", "caching", "promise", "ReactPHP"], "license": "MIT", + "authors": [ + { + "name": "Christian Lück", + "homepage": "https://clue.engineering/", + "email": "christian@clue.engineering" + }, + { + "name": "Cees-Jan Kiewiet", + "homepage": "https://wyrihaximus.net/", + "email": "reactphp@ceesjankiewiet.nl" + }, + { + "name": "Jan Sorgalla", + "homepage": "https://sorgalla.com/", + "email": "jsorgalla@gmail.com" + }, + { + "name": "Chris Boden", + "homepage": "https://cboden.dev/", + "email": "cboden@gmail.com" + } + ], "require": { "php": ">=5.3.0", "react/promise": "^3.0 || ^2.0 || ^1.1" From 42298c7893216157512375134be21d11160cdbb8 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Fri, 28 Aug 2020 14:18:23 +0200 Subject: [PATCH 56/78] Update PHPUnit configuration schema for PHPUnit 9.3 --- .gitattributes | 1 + .travis.yml | 5 +++-- composer.json | 2 +- phpunit.xml.dist | 16 ++++++++++------ phpunit.xml.legacy | 18 ++++++++++++++++++ 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 phpunit.xml.legacy diff --git a/.gitattributes b/.gitattributes index 982c460..e9da46b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,5 @@ /.gitignore export-ignore /.travis.yml export-ignore /phpunit.xml.dist export-ignore +/phpunit.xml.legacy export-ignore /tests export-ignore diff --git a/.travis.yml b/.travis.yml index 1c59030..5623330 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,8 @@ jobs: - php: hhvm-3.18 install: - - composer install --no-interaction + - composer install script: - - vendor/bin/phpunit --coverage-text + - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi + - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi diff --git a/composer.json b/composer.json index 480b1ec..ad0d9fe 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,6 @@ "psr-4": { "React\\Tests\\Cache\\": "tests/" } }, "require-dev": { - "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0e947b8..fa88e7e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,19 @@ - + + ./tests/ - - - + + ./src/ - - + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy new file mode 100644 index 0000000..fbb43e8 --- /dev/null +++ b/phpunit.xml.legacy @@ -0,0 +1,18 @@ + + + + + + + ./tests/ + + + + + ./src/ + + + From 44a568925556b0bd8cacc7b49fb0f1cf0d706a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 18 Sep 2020 14:12:35 +0200 Subject: [PATCH 57/78] Prepare v1.1.0 release --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ecd1c..56ddc4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.1.0 (2020-09-18) + +* Feature: Forward compatibility with react/promise 3. + (#39 by @WyriHaximus) + +* Add `.gitattributes` to exclude dev files from exports. + (#40 by @reedy) + +* Improve test suite, update to support PHP 8 and PHPUnit 9.3. + (#41 and #43 by @SimonFrings and #42 by @WyriHaximus) + ## 1.0.0 (2019-07-11) * First stable LTS release, now following [SemVer](https://semver.org/). diff --git a/README.md b/README.md index 74cef54..2986e25 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/cache:^1.0 +$ composer require react/cache:^1.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From f55ee41827609116138f709e84c0aca2c73af690 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 1 Feb 2021 16:39:59 +0100 Subject: [PATCH 58/78] Align DocBlock Promise return types Currently most of the docblocks provide a return type inside the PromiseInterface like bool or array. This commit aligns it for all methods on the interface. --- src/CacheInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 3d52501..8e51c19 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -26,7 +26,7 @@ interface CacheInterface * * @param string $key * @param mixed $default Default value to return for cache miss or null if not given. - * @return PromiseInterface + * @return PromiseInterface */ public function get($key, $default = null); @@ -72,7 +72,7 @@ public function get($key, $default = null); * @param string $key * @param mixed $value * @param ?float $ttl - * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function set($key, $value, $ttl = null); @@ -93,7 +93,7 @@ public function set($key, $value, $ttl = null); * 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 + * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function delete($key); From 54afe31079f9ed246cbadb77b820eb72a8dc368a Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 3 Feb 2021 12:55:42 +0100 Subject: [PATCH 59/78] Use GitHub actions for continuous integration (CI) Bye bye Travis CI, you've served us well. --- .gitattributes | 2 +- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 27 ------------------------ README.md | 2 +- 4 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index e9da46b..edb8a01 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ /.gitattributes export-ignore +/.github/ export-ignore /.gitignore export-ignore -/.travis.yml export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d321f38 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + +jobs: + PHPUnit: + name: PHPUnit (PHP ${{ matrix.php }}) + runs-on: ubuntu-20.04 + strategy: + matrix: + php: + - 7.4 + - 7.3 + - 7.2 + - 7.1 + - 7.0 + - 5.6 + - 5.5 + - 5.4 + - 5.3 + steps: + - uses: actions/checkout@v2 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + - run: composer install + - run: vendor/bin/phpunit --coverage-text + if: ${{ matrix.php >= 7.3 }} + - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy + if: ${{ matrix.php < 7.3 }} + + PHPUnit-hhvm: + name: PHPUnit (HHVM) + runs-on: ubuntu-18.04 + continue-on-error: true + steps: + - uses: actions/checkout@v2 + - uses: azjezz/setup-hhvm@v1 + with: + version: lts-3.30 + - run: hhvm $(which composer) install + - run: hhvm vendor/bin/phpunit diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5623330..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: php - -# lock distro so new future defaults will not break the build -dist: trusty - -jobs: - include: - - php: 5.3 - dist: precise - - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4 - - php: hhvm-3.18 - allow_failures: - - php: hhvm-3.18 - -install: - - composer install - -script: - - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi - - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi diff --git a/README.md b/README.md index 2986e25..89b4f22 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cache -[![Build Status](https://travis-ci.org/reactphp/cache.svg?branch=master)](https://travis-ci.org/reactphp/cache) +[![CI status](https://github.com/reactphp/cache/workflows/CI/badge.svg)](https://github.com/reactphp/cache/actions) Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). From a7ddc610a8a1426fa04a069656900251cbd6083f Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 3 Feb 2021 13:00:23 +0100 Subject: [PATCH 60/78] Support PHP 8 --- .github/workflows/ci.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d321f38..cf214c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.0 - 7.4 - 7.3 - 7.2 diff --git a/README.md b/README.md index 89b4f22..71dde5c 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,7 @@ $ composer require react/cache:^1.1 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 +extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's *highly recommended to use PHP 7+* for this project. From 7b9b3c7c48bc771cae9d573dfac6073f69fd5bf9 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Mon, 15 Nov 2021 16:58:38 +0100 Subject: [PATCH 61/78] Support PHP 8.1 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf214c8..c64ef6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.1 - 8.0 - 7.4 - 7.3 From 6a0ec9eedf6773b991a13a7475c0de73a2dd696a Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 16 Mar 2022 14:14:09 +0100 Subject: [PATCH 62/78] Add badge to show number of project installations --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 71dde5c..48b6e6c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Cache [![CI status](https://github.com/reactphp/cache/workflows/CI/badge.svg)](https://github.com/reactphp/cache/actions) +[![installs on Packagist](https://img.shields.io/packagist/dt/react/cache?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/cache) Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). From a11235cb8a447093310c4035293d3bfbc0e0a408 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 12 Apr 2022 10:38:49 +0200 Subject: [PATCH 63/78] Fix legacy HHVM build by downgrading Composer --- .github/workflows/ci.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c64ef6a..2757783 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,5 +43,6 @@ jobs: - uses: azjezz/setup-hhvm@v1 with: version: lts-3.30 + - run: composer self-update --2.2 # downgrade Composer for HHVM - run: hhvm $(which composer) install - run: hhvm vendor/bin/phpunit diff --git a/README.md b/README.md index 48b6e6c..c055b7a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cache -[![CI status](https://github.com/reactphp/cache/workflows/CI/badge.svg)](https://github.com/reactphp/cache/actions) +[![CI status](https://github.com/reactphp/cache/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/cache/actions) [![installs on Packagist](https://img.shields.io/packagist/dt/react/cache?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/cache) Async, [Promise](https://github.com/reactphp/promise)-based cache interface From 08450788d7083b445aa90798ec3b603de47bc1c3 Mon Sep 17 00:00:00 2001 From: Nicolas Hedger Date: Mon, 20 Jun 2022 16:50:38 +0200 Subject: [PATCH 64/78] chore(docs): remove leading dollar sign --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c055b7a..26c6c61 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -$ composer require react/cache:^1.1 +composer require react/cache:^1.1 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. @@ -353,13 +353,13 @@ 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 +composer install ``` To run the test suite, go to the project root and run: ```bash -$ php vendor/bin/phpunit +vendor/bin/phpunit ``` ## License From 19886b1e4daa68e4f6ff6e4f804f5f2005df216f Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sun, 14 Aug 2022 00:19:44 +0200 Subject: [PATCH 65/78] Test on PHP 8.2 With PHP 8.2 coming out later this year, we should be reading for it's release to ensure all out code works on it. Refs: https://github.com/reactphp/event-loop/pull/258 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2757783..0724232 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.2 - 8.1 - 8.0 - 7.4 From 1b7b7062caba9ada42f01f241971b9751e60be36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 14 Nov 2022 09:34:08 +0100 Subject: [PATCH 66/78] Update test suite and report failed assertions --- .github/workflows/ci.yml | 22 +++++++++++++--------- composer.json | 14 +++++++++----- phpunit.xml.dist | 17 +++++++++++++---- phpunit.xml.legacy | 10 +++++++++- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0724232..55bbaa5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: php: @@ -24,11 +24,12 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} coverage: xdebug + ini-file: development - run: composer install - run: vendor/bin/phpunit --coverage-text if: ${{ matrix.php >= 7.3 }} @@ -37,13 +38,16 @@ jobs: PHPUnit-hhvm: name: PHPUnit (HHVM) - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v2 - - uses: azjezz/setup-hhvm@v1 + - uses: actions/checkout@v3 + - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM + - name: Run hhvm composer.phar install + uses: docker://hhvm/hhvm:3.30-lts-latest with: - version: lts-3.30 - - run: composer self-update --2.2 # downgrade Composer for HHVM - - run: hhvm $(which composer) install - - run: hhvm vendor/bin/phpunit + args: hhvm composer.phar install + - name: Run hhvm vendor/bin/phpunit + uses: docker://hhvm/hhvm:3.30-lts-latest + with: + args: hhvm vendor/bin/phpunit diff --git a/composer.json b/composer.json index ad0d9fe..153439a 100644 --- a/composer.json +++ b/composer.json @@ -29,13 +29,17 @@ "php": ">=5.3.0", "react/promise": "^3.0 || ^2.0 || ^1.1" }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, "autoload": { - "psr-4": { "React\\Cache\\": "src/" } + "psr-4": { + "React\\Cache\\": "src/" + } }, "autoload-dev": { - "psr-4": { "React\\Tests\\Cache\\": "tests/" } - }, - "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + "psr-4": { + "React\\Tests\\Cache\\": "tests/" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fa88e7e..7a9577e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,11 +1,12 @@ - - + + convertDeprecationsToExceptions="true"> ./tests/ @@ -16,4 +17,12 @@ ./src/ + + + + + + + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index fbb43e8..ac5600a 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -1,6 +1,6 @@ - + ./src/ + + + + + + + + From d47c472b64aa5608225f47965a484b75c7817d5b Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 30 Nov 2022 16:59:55 +0100 Subject: [PATCH 67/78] Prepare v1.2.0 release --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ddc4a..ab59f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.2.0 (2022-11-30) + +* Feature: Support PHP 8.1 and PHP 8.2. + (#47 by @SimonFrings and #52 by @WyriHaximus) + +* Minor documentation improvements. + (#48 by @SimonFrings and #51 by @nhedger) + +* Update test suite and use GitHub actions for continuous integration (CI). + (#45 and #49 by @SimonFrings and #54 by @clue) + ## 1.1.0 (2020-09-18) * Feature: Forward compatibility with react/promise 3. diff --git a/README.md b/README.md index 26c6c61..7a86be9 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -composer require react/cache:^1.1 +composer require react/cache:^1.2 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 66bbb36db38d52df1da5c643095d73a9363358e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 30 Sep 2023 13:17:37 +0200 Subject: [PATCH 68/78] Run tests on PHP 8.3 and update test suite --- .github/workflows/ci.yml | 9 +++++---- composer.json | 2 +- phpunit.xml.dist | 6 +++--- phpunit.xml.legacy | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55bbaa5..c3fc411 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.3 - 8.2 - 8.1 - 8.0 @@ -24,7 +25,7 @@ jobs: - 5.4 - 5.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -41,12 +42,12 @@ jobs: runs-on: ubuntu-22.04 continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - - name: Run hhvm composer.phar install + - name: Run hhvm composer.phar require react/promise:^2 # downgrade Promise for HHVM uses: docker://hhvm/hhvm:3.30-lts-latest with: - args: hhvm composer.phar install + args: hhvm composer.phar require react/promise:^2 - name: Run hhvm vendor/bin/phpunit uses: docker://hhvm/hhvm:3.30-lts-latest with: diff --git a/composer.json b/composer.json index 153439a..4924c2e 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7a9577e..ac542e7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,8 @@ - + - + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index ac5600a..8916116 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -18,7 +18,7 @@ - + From 8eafbd1f5a745d39a4bbcdddb303b1b7920146ae Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 30 Jan 2024 18:19:06 +0100 Subject: [PATCH 69/78] Hello `3.x` development branch Once this PR is merged, we can start working on the new [v3.0.0 milestone](https://github.com/reactphp/cache/milestone/10). The default branch will be `3.x` and the old `1.x` branch still stay in place at least until `3.0.0` is released. Refs: Road map ticket for cache: #56 Plans for ReactPHP v3: https://github.com/orgs/reactphp/discussions/481 PR templated from: https://github.com/friends-of-reactphp/mysql/pull/185 --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a86be9..2213c57 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ Async, [Promise](https://github.com/reactphp/promise)-based cache interface for [ReactPHP](https://reactphp.org/). +> **Development version:** This branch contains the code for the upcoming v3 +> release. For the code of the current stable v1 release, check out the +> [`1.x` branch](https://github.com/reactphp/cache/tree/1.x). +> +> The upcoming v3 release will be the way forward for this package. However, +> we will still actively support v1 for those not yet on the latest version. +> See also [installation instructions](#install) for more details. + The cache component provides a [Promise](https://github.com/reactphp/promise)-based [`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache) @@ -333,11 +341,11 @@ fetched from the database. The recommended way to install this library is [through Composer](https://getcomposer.org). [New to Composer?](https://getcomposer.org/doc/00-intro.md) -This project follows [SemVer](https://semver.org/). -This will install the latest supported version: +Once released, this project will follow [SemVer](https://semver.org/). +At the moment, this will install the latest development version: ```bash -composer require react/cache:^1.2 +composer require react/cache:^3@dev ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From e2e0e06c1446906da85044763768d5861080cc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 3 Feb 2024 12:20:53 +0100 Subject: [PATCH 70/78] Update to require PHP 7.1+ --- .github/workflows/ci.yml | 21 --------------------- README.md | 5 ++--- composer.json | 4 ++-- phpunit.xml.legacy | 2 +- 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3fc411..6f9cfb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,11 +19,6 @@ jobs: - 7.3 - 7.2 - 7.1 - - 7.0 - - 5.6 - - 5.5 - - 5.4 - - 5.3 steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 @@ -36,19 +31,3 @@ jobs: if: ${{ matrix.php >= 7.3 }} - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy if: ${{ matrix.php < 7.3 }} - - PHPUnit-hhvm: - name: PHPUnit (HHVM) - runs-on: ubuntu-22.04 - continue-on-error: true - steps: - - uses: actions/checkout@v4 - - run: cp "$(which composer)" composer.phar && ./composer.phar self-update --2.2 # downgrade Composer for HHVM - - name: Run hhvm composer.phar require react/promise:^2 # downgrade Promise for HHVM - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm composer.phar require react/promise:^2 - - name: Run hhvm vendor/bin/phpunit - uses: docker://hhvm/hhvm:3.30-lts-latest - with: - args: hhvm vendor/bin/phpunit diff --git a/README.md b/README.md index 2213c57..0468f0c 100644 --- a/README.md +++ b/README.md @@ -351,9 +351,8 @@ composer require react/cache:^3@dev 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 8+ and -HHVM. -It's *highly recommended to use PHP 7+* for this project. +extensions and supports running on PHP 7.1 through current PHP 8+. +It's *highly recommended to use the latest supported PHP version* for this project. ## Tests diff --git a/composer.json b/composer.json index 4924c2e..db7f396 100644 --- a/composer.json +++ b/composer.json @@ -26,11 +26,11 @@ } ], "require": { - "php": ">=5.3.0", + "php": ">=7.1", "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index 8916116..a018d7a 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ From 0a0a214545c9d83b06318822dd5b47ba6355e121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 10 Feb 2024 20:10:18 +0100 Subject: [PATCH 71/78] Update PHP language syntax, documentation and tests --- README.md | 8 ++++---- composer.json | 2 +- phpunit.xml.legacy | 2 +- src/ArrayCache.php | 34 +++++++++++++++++----------------- src/CacheInterface.php | 4 ++-- tests/ArrayCacheTest.php | 16 ++++++++-------- tests/TestCase.php | 9 +++++---- 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 0468f0c..cf6a8ea 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Similarly, an expired cache item (once the time-to-live is expired) is considered a cache miss. ```php -$cache->getMultiple(array('name', 'age'))->then(function (array $values) { +$cache->getMultiple(['name', 'age'])->then(function (array $values) { $name = $values['name'] ?? 'User'; $age = $values['age'] ?? 'n/a'; @@ -170,7 +170,7 @@ supports. Trying to access an expired cache items results in a cache miss, see also [`getMultiple()`](#getmultiple). ```php -$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); +$cache->setMultiple(['foo' => 1, 'bar' => 2], 60); ``` This example eventually sets the list of values - the key `foo` to `1` value @@ -187,7 +187,7 @@ to `true`. If the cache implementation has to go over the network to delete it, it may take a while. ```php -$cache->deleteMultiple(array('foo', 'bar, 'baz')); +$cache->deleteMultiple(['foo', 'bar, 'baz']); ``` This example eventually deletes keys `foo`, `bar` and `baz` from the cache. @@ -322,7 +322,7 @@ public function getAndCacheFooFromDb() { return $this->db ->get('foo') - ->then(array($this, 'cacheFooFromDb')); + ->then([$this, 'cacheFooFromDb']); } public function cacheFooFromDb($foo) diff --git a/composer.json b/composer.json index db7f396..25983ae 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "react/promise": "^3.0 || ^2.0 || ^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7" + "phpunit/phpunit": "^9.6 || ^7.5" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index a018d7a..0086860 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -2,7 +2,7 @@ diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 81f25ef..ed97c0c 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -2,14 +2,14 @@ namespace React\Cache; -use React\Promise; -use React\Promise\PromiseInterface; +use function React\Promise\all; +use function React\Promise\resolve; class ArrayCache implements CacheInterface { private $limit; - private $data = array(); - private $expires = array(); + private $data = []; + private $expires = []; private $supportsHighResolution; /** @@ -65,7 +65,7 @@ public function get($key, $default = null) } if (!\array_key_exists($key, $this->data)) { - return Promise\resolve($default); + return resolve($default); } // remove and append to end of array to keep track of LRU info @@ -73,7 +73,7 @@ public function get($key, $default = null) unset($this->data[$key]); $this->data[$key] = $value; - return Promise\resolve($value); + return resolve($value); } public function set($key, $value, $ttl = null) @@ -105,25 +105,25 @@ public function set($key, $value, $ttl = null) unset($this->data[$key], $this->expires[$key]); } - return Promise\resolve(true); + return resolve(true); } public function delete($key) { unset($this->data[$key], $this->expires[$key]); - return Promise\resolve(true); + return resolve(true); } public function getMultiple(array $keys, $default = null) { - $values = array(); + $values = []; foreach ($keys as $key) { $values[$key] = $this->get($key, $default); } - return Promise\all($values); + return all($values); } public function setMultiple(array $values, $ttl = null) @@ -132,7 +132,7 @@ public function setMultiple(array $values, $ttl = null) $this->set($key, $value, $ttl); } - return Promise\resolve(true); + return resolve(true); } public function deleteMultiple(array $keys) @@ -141,15 +141,15 @@ public function deleteMultiple(array $keys) unset($this->data[$key], $this->expires[$key]); } - return Promise\resolve(true); + return resolve(true); } public function clear() { - $this->data = array(); - $this->expires = array(); + $this->data = []; + $this->expires = []; - return Promise\resolve(true); + return resolve(true); } public function has($key) @@ -160,7 +160,7 @@ public function has($key) } if (!\array_key_exists($key, $this->data)) { - return Promise\resolve(false); + return resolve(false); } // remove and append to end of array to keep track of LRU info @@ -168,7 +168,7 @@ public function has($key) unset($this->data[$key]); $this->data[$key] = $value; - return Promise\resolve(true); + return resolve(true); } /** diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 8e51c19..04beb69 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -106,7 +106,7 @@ public function delete($key); * considered a cache miss. * * ```php - * $cache->getMultiple(array('name', 'age'))->then(function (array $values) { + * $cache->getMultiple(['name', 'age'])->then(function (array $values) { * $name = $values['name'] ?? 'User'; * $age = $values['age'] ?? 'n/a'; * @@ -138,7 +138,7 @@ public function getMultiple(array $keys, $default = null); * see also [`get()`](#get). * * ```php - * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60); + * $cache->setMultiple(['foo' => 1, 'bar' => 2], 60); * ``` * * This example eventually sets the list of values - the key `foo` to 1 value diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index 420d45f..be86783 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -203,27 +203,27 @@ public function testGetMultiple() $this->cache->set('foo', '1'); $this->cache - ->getMultiple(array('foo', 'bar'), 'baz') - ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz'))); + ->getMultiple(['foo', 'bar'], 'baz') + ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => 'baz'])); } public function testSetMultiple() { $this->cache = new ArrayCache(); - $this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10); + $this->cache->setMultiple(['foo' => '1', 'bar' => '2'], 10); $this->cache - ->getMultiple(array('foo', 'bar')) - ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2'))); + ->getMultiple(['foo', 'bar']) + ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => '2'])); } public function testDeleteMultiple() { $this->cache = new ArrayCache(); - $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3)); + $this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]); $this->cache - ->deleteMultiple(array('foo', 'baz')) + ->deleteMultiple(['foo', 'baz']) ->then($this->expectCallableOnceWith(true)); $this->cache @@ -242,7 +242,7 @@ public function testDeleteMultiple() public function testClearShouldClearCache() { $this->cache = new ArrayCache(); - $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3)); + $this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]); $this->cache->clear(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 53597e0..45aa5c2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -49,12 +49,13 @@ protected function expectCallableNever() protected function createCallableMock() { - if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + $builder = $this->getMockBuilder(\stdClass::class); + if (method_exists($builder, 'addMethods')) { // PHPUnit 9+ - return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + return $builder->addMethods(['__invoke'])->getMock(); } else { - // legacy PHPUnit 4 - PHPUnit 9 - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + // legacy PHPUnit + return $builder->setMethods(['__invoke'])->getMock(); } } } From 65d16441c6954e831221ee5117df94cafb05717d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 3 Feb 2024 13:17:40 +0100 Subject: [PATCH 72/78] Update to require Promise v3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 25983ae..754be64 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ ], "require": { "php": ">=7.1", - "react/promise": "^3.0 || ^2.0 || ^1.1" + "react/promise": "^3.0" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^7.5" From 7832707972f1f4541ffdfdf0662dcc7403a32df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 15 May 2024 20:04:27 +0200 Subject: [PATCH 73/78] Add native types to public API --- src/ArrayCache.php | 24 +++++++++++------------- src/CacheInterface.php | 16 ++++++++-------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/ArrayCache.php b/src/ArrayCache.php index ed97c0c..786cdfc 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -2,6 +2,7 @@ namespace React\Cache; +use React\Promise\PromiseInterface; use function React\Promise\all; use function React\Promise\resolve; @@ -49,7 +50,7 @@ class ArrayCache implements CacheInterface * * @param int|null $limit maximum number of entries to store in the LRU cache */ - public function __construct($limit = null) + public function __construct(?int $limit = null) { $this->limit = $limit; @@ -57,7 +58,7 @@ public function __construct($limit = null) $this->supportsHighResolution = \function_exists('hrtime'); } - public function get($key, $default = null) + public function get(string $key, $default = null): PromiseInterface { // delete key if it is already expired => below will detect this as a cache miss if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { @@ -76,7 +77,7 @@ public function get($key, $default = null) return resolve($value); } - public function set($key, $value, $ttl = null) + public function set(string $key, $value, ?float $ttl = null): PromiseInterface { // unset before setting to ensure this entry will be added to end of array (LRU info) unset($this->data[$key]); @@ -108,14 +109,14 @@ public function set($key, $value, $ttl = null) return resolve(true); } - public function delete($key) + public function delete(string $key): PromiseInterface { unset($this->data[$key], $this->expires[$key]); return resolve(true); } - public function getMultiple(array $keys, $default = null) + public function getMultiple(array $keys, $default = null): PromiseInterface { $values = []; @@ -126,7 +127,7 @@ public function getMultiple(array $keys, $default = null) return all($values); } - public function setMultiple(array $values, $ttl = null) + public function setMultiple(array $values, ?float $ttl = null): PromiseInterface { foreach ($values as $key => $value) { $this->set($key, $value, $ttl); @@ -135,7 +136,7 @@ public function setMultiple(array $values, $ttl = null) return resolve(true); } - public function deleteMultiple(array $keys) + public function deleteMultiple(array $keys): PromiseInterface { foreach ($keys as $key) { unset($this->data[$key], $this->expires[$key]); @@ -144,7 +145,7 @@ public function deleteMultiple(array $keys) return resolve(true); } - public function clear() + public function clear(): PromiseInterface { $this->data = []; $this->expires = []; @@ -152,7 +153,7 @@ public function clear() return resolve(true); } - public function has($key) + public function has(string $key): PromiseInterface { // delete key if it is already expired if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) { @@ -171,10 +172,7 @@ public function has($key) return resolve(true); } - /** - * @return float - */ - private function now() + private function now(): float { return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 04beb69..4a486fb 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -28,7 +28,7 @@ interface CacheInterface * @param mixed $default Default value to return for cache miss or null if not given. * @return PromiseInterface */ - public function get($key, $default = null); + public function get(string $key, $default = null): PromiseInterface; /** * Stores an item in the cache. @@ -74,7 +74,7 @@ public function get($key, $default = null); * @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); + public function set(string $key, $value, ?float $ttl = null): PromiseInterface; /** * Deletes an item from the cache. @@ -95,7 +95,7 @@ public function set($key, $value, $ttl = null); * @param string $key * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function delete($key); + public function delete(string $key): PromiseInterface; /** * Retrieves multiple cache items by their unique keys. @@ -122,7 +122,7 @@ public function delete($key); * @param mixed $default Default value to return for keys that do not exist. * @return PromiseInterface Returns a promise which resolves to an `array` of cached values */ - public function getMultiple(array $keys, $default = null); + public function getMultiple(array $keys, $default = null): PromiseInterface; /** * Persists a set of key => value pairs in the cache, with an optional TTL. @@ -148,7 +148,7 @@ public function getMultiple(array $keys, $default = null); * @param ?float $ttl Optional. The TTL value of this item. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function setMultiple(array $values, $ttl = null); + public function setMultiple(array $values, ?float $ttl = null): PromiseInterface; /** * Deletes multiple cache items in a single operation. @@ -156,14 +156,14 @@ public function setMultiple(array $values, $ttl = null); * @param string[] $keys A list of string-based keys to be deleted. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function deleteMultiple(array $keys); + public function deleteMultiple(array $keys): PromiseInterface; /** * Wipes clean the entire cache. * * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function clear(); + public function clear(): PromiseInterface; /** * Determines whether an item is present in the cache. @@ -190,5 +190,5 @@ public function clear(); * @param string $key The cache item key. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function has($key); + public function has(string $key): PromiseInterface; } From d68c6d4dc2a5aac08946199e989625fa3f2656b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 20 May 2024 11:25:46 +0200 Subject: [PATCH 74/78] Add PHPStan to test environment with `max` level --- .gitattributes | 1 + .github/workflows/ci.yml | 23 +++++++++ README.md | 8 ++- composer.json | 1 + phpstan.neon.dist | 6 +++ src/ArrayCache.php | 8 +++ src/CacheInterface.php | 8 +-- tests/ArrayCacheTest.php | 105 ++++++++++++--------------------------- tests/TestCase.php | 36 +++++--------- 9 files changed, 93 insertions(+), 103 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index edb8a01..3dd9bf4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ /.gitattributes export-ignore /.github/ export-ignore /.gitignore export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f9cfb4..587bd0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,3 +31,26 @@ jobs: if: ${{ matrix.php >= 7.3 }} - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy if: ${{ matrix.php < 7.3 }} + + PHPStan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - 8.3 + - 8.2 + - 8.1 + - 8.0 + - 7.4 + - 7.3 + - 7.2 + - 7.1 + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - run: composer install + - run: vendor/bin/phpstan diff --git a/README.md b/README.md index cf6a8ea..152277b 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Similarly, an expired cache item (once the time-to-live is expired) is considered a cache miss. ```php -$cache->getMultiple(['name', 'age'])->then(function (array $values) { +$cache->getMultiple(['name', 'age'])->then(function (array $values): void { $name = $values['name'] ?? 'User'; $age = $values['age'] ?? 'n/a'; @@ -369,6 +369,12 @@ To run the test suite, go to the project root and run: vendor/bin/phpunit ``` +On top of this, we use PHPStan on max level to ensure type safety across the project: + +```bash +vendor/bin/phpstan +``` + ## License MIT, see [LICENSE file](LICENSE). diff --git a/composer.json b/composer.json index 754be64..1cebfcf 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "react/promise": "^3.0" }, "require-dev": { + "phpstan/phpstan": "1.11.1 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "autoload": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..895c841 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +parameters: + level: max + + paths: + - src/ + - tests/ diff --git a/src/ArrayCache.php b/src/ArrayCache.php index 786cdfc..d52bee9 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -8,9 +8,16 @@ class ArrayCache implements CacheInterface { + /** @var ?int */ private $limit; + + /** @var array */ private $data = []; + + /** @var array */ private $expires = []; + + /** @var bool */ private $supportsHighResolution; /** @@ -124,6 +131,7 @@ public function getMultiple(array $keys, $default = null): PromiseInterface $values[$key] = $this->get($key, $default); } + /** @var PromiseInterface> */ return all($values); } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 4a486fb..278108a 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -106,7 +106,7 @@ public function delete(string $key): PromiseInterface; * considered a cache miss. * * ```php - * $cache->getMultiple(['name', 'age'])->then(function (array $values) { + * $cache->getMultiple(['name', 'age'])->then(function (array $values): void { * $name = $values['name'] ?? 'User'; * $age = $values['age'] ?? 'n/a'; * @@ -120,7 +120,7 @@ public function delete(string $key): PromiseInterface; * * @param string[] $keys A list of keys that can obtained in a single operation. * @param mixed $default Default value to return for keys that do not exist. - * @return PromiseInterface Returns a promise which resolves to an `array` of cached values + * @return PromiseInterface> Returns a promise which resolves to an `array` of cached values */ public function getMultiple(array $keys, $default = null): PromiseInterface; @@ -144,8 +144,8 @@ public function getMultiple(array $keys, $default = null): PromiseInterface; * This example eventually sets the list of values - the key `foo` to 1 value * and the key `bar` to 2. If some of the keys already exist, they are overridden. * - * @param array $values A list of key => value pairs for a multiple-set operation. - * @param ?float $ttl Optional. The TTL value of this item. + * @param array $values A list of key => value pairs for a multiple-set operation. + * @param ?float $ttl Optional. The TTL value of this item. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ public function setMultiple(array $values, ?float $ttl = null): PromiseInterface; diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index be86783..e46c726 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -14,93 +14,50 @@ class ArrayCacheTest extends TestCase /** * @before */ - public function setUpArrayCache() + public function setUpArrayCache(): void { $this->cache = new ArrayCache(); } /** @test */ - public function getShouldResolvePromiseWithNullForNonExistentKey() + public function getShouldResolvePromiseWithNullForNonExistentKey(): void { - $success = $this->createCallableMock(); - $success - ->expects($this->once()) - ->method('__invoke') - ->with(null); - - $this->cache - ->get('foo') - ->then( - $success, - $this->expectCallableNever() - ); + $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } /** @test */ - public function setShouldSetKey() + public function setShouldSetKey(): void { - $setPromise = $this->cache - ->set('foo', 'bar'); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(true)); + $this->cache->set('foo', 'bar')->then($this->expectCallableOnceWith(true)); - $setPromise->then($mock); - - $success = $this->createCallableMock(); - $success - ->expects($this->once()) - ->method('__invoke') - ->with('bar'); - - $this->cache - ->get('foo') - ->then($success); + $this->cache->get('foo')->then($this->expectCallableOnceWith('bar')); } /** @test */ - public function deleteShouldDeleteKey() + public function deleteShouldDeleteKey(): void { - $this->cache - ->set('foo', 'bar'); - - $deletePromise = $this->cache - ->delete('foo'); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(true)); + $this->cache->set('foo', 'bar'); - $deletePromise->then($mock); + $this->cache->delete('foo')->then($this->expectCallableOnceWith(true)); - $this->cache - ->get('foo') - ->then( - $this->expectCallableOnce(), - $this->expectCallableNever() - ); + $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - public function testGetWillResolveWithNullForCacheMiss() + public function testGetWillResolveWithNullForCacheMiss(): void { $this->cache = new ArrayCache(); $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - public function testGetWillResolveWithDefaultValueForCacheMiss() + public function testGetWillResolveWithDefaultValueForCacheMiss(): void { $this->cache = new ArrayCache(); $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar')); } - public function testGetWillResolveWithExplicitNullValueForCacheHit() + public function testGetWillResolveWithExplicitNullValueForCacheHit(): void { $this->cache = new ArrayCache(); @@ -108,7 +65,7 @@ public function testGetWillResolveWithExplicitNullValueForCacheHit() $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null)); } - public function testLimitSizeToZeroDoesNotStoreAnyData() + public function testLimitSizeToZeroDoesNotStoreAnyData(): void { $this->cache = new ArrayCache(0); @@ -117,7 +74,7 @@ public function testLimitSizeToZeroDoesNotStoreAnyData() $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - public function testLimitSizeToOneWillOnlyReturnLastWrite() + public function testLimitSizeToOneWillOnlyReturnLastWrite(): void { $this->cache = new ArrayCache(1); @@ -128,7 +85,7 @@ public function testLimitSizeToOneWillOnlyReturnLastWrite() $this->cache->get('bar')->then($this->expectCallableOnceWith('2')); } - public function testOverwriteWithLimitedSizeWillUpdateLRUInfo() + public function testOverwriteWithLimitedSizeWillUpdateLRUInfo(): void { $this->cache = new ArrayCache(2); @@ -142,7 +99,7 @@ public function testOverwriteWithLimitedSizeWillUpdateLRUInfo() $this->cache->get('baz')->then($this->expectCallableOnceWith('4')); } - public function testGetWithLimitedSizeWillUpdateLRUInfo() + public function testGetWithLimitedSizeWillUpdateLRUInfo(): void { $this->cache = new ArrayCache(2); @@ -156,7 +113,7 @@ public function testGetWithLimitedSizeWillUpdateLRUInfo() $this->cache->get('baz')->then($this->expectCallableOnceWith('3')); } - public function testGetWillResolveWithValueIfItemIsNotExpired() + public function testGetWillResolveWithValueIfItemIsNotExpired(): void { $this->cache = new ArrayCache(); @@ -165,7 +122,7 @@ public function testGetWillResolveWithValueIfItemIsNotExpired() $this->cache->get('foo')->then($this->expectCallableOnceWith('1')); } - public function testGetWillResolveWithDefaultIfItemIsExpired() + public function testGetWillResolveWithDefaultIfItemIsExpired(): void { $this->cache = new ArrayCache(); @@ -174,7 +131,7 @@ public function testGetWillResolveWithDefaultIfItemIsExpired() $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - public function testSetWillOverwritOldestItemIfNoEntryIsExpired() + public function testSetWillOverwritOldestItemIfNoEntryIsExpired(): void { $this->cache = new ArrayCache(2); @@ -185,7 +142,7 @@ public function testSetWillOverwritOldestItemIfNoEntryIsExpired() $this->cache->get('foo')->then($this->expectCallableOnceWith(null)); } - public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired() + public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired(): void { $this->cache = new ArrayCache(2); @@ -197,7 +154,7 @@ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired() $this->cache->get('bar')->then($this->expectCallableOnceWith(null)); } - public function testGetMultiple() + public function testGetMultiple(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', '1'); @@ -207,7 +164,7 @@ public function testGetMultiple() ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => 'baz'])); } - public function testSetMultiple() + public function testSetMultiple(): void { $this->cache = new ArrayCache(); $this->cache->setMultiple(['foo' => '1', 'bar' => '2'], 10); @@ -217,7 +174,7 @@ public function testSetMultiple() ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => '2'])); } - public function testDeleteMultiple() + public function testDeleteMultiple(): void { $this->cache = new ArrayCache(); $this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]); @@ -239,7 +196,7 @@ public function testDeleteMultiple() ->then($this->expectCallableOnceWith(false)); } - public function testClearShouldClearCache() + public function testClearShouldClearCache(): void { $this->cache = new ArrayCache(); $this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]); @@ -259,7 +216,7 @@ public function testClearShouldClearCache() ->then($this->expectCallableOnceWith(false)); } - public function hasShouldResolvePromiseForExistingKey() + public function hasShouldResolvePromiseForExistingKey(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', 'bar'); @@ -269,7 +226,7 @@ public function hasShouldResolvePromiseForExistingKey() ->then($this->expectCallableOnceWith(true)); } - public function hasShouldResolvePromiseForNonExistentKey() + public function hasShouldResolvePromiseForNonExistentKey(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', 'bar'); @@ -279,7 +236,7 @@ public function hasShouldResolvePromiseForNonExistentKey() ->then($this->expectCallableOnceWith(false)); } - public function testHasWillResolveIfItemIsNotExpired() + public function testHasWillResolveIfItemIsNotExpired(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', '1', 10); @@ -289,7 +246,7 @@ public function testHasWillResolveIfItemIsNotExpired() ->then($this->expectCallableOnceWith(true)); } - public function testHasWillResolveIfItemIsExpired() + public function testHasWillResolveIfItemIsExpired(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', '1', 0); @@ -299,7 +256,7 @@ public function testHasWillResolveIfItemIsExpired() ->then($this->expectCallableOnceWith(false)); } - public function testHasWillResolveForExplicitNullValue() + public function testHasWillResolveForExplicitNullValue(): void { $this->cache = new ArrayCache(); $this->cache->set('foo', null); @@ -309,7 +266,7 @@ public function testHasWillResolveForExplicitNullValue() ->then($this->expectCallableOnceWith(true)); } - public function testHasWithLimitedSizeWillUpdateLRUInfo() + public function testHasWithLimitedSizeWillUpdateLRUInfo(): void { $this->cache = new ArrayCache(2); diff --git a/tests/TestCase.php b/tests/TestCase.php index 45aa5c2..9088454 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,52 +2,40 @@ namespace React\Tests\Cache; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase as BaseTestCase; class TestCase extends BaseTestCase { - protected function expectCallableExactly($amount) + protected function expectCallableOnce(): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->exactly($amount)) - ->method('__invoke'); + $mock->expects($this->once())->method('__invoke'); + assert(is_callable($mock)); return $mock; } - protected function expectCallableOnce() + /** @param mixed $argument */ + protected function expectCallableOnceWith($argument): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke'); + $mock->expects($this->once())->method('__invoke')->with($argument); + assert(is_callable($mock)); return $mock; } - protected function expectCallableOnceWith($param) + protected function expectCallableNever(): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($param); + $mock->expects($this->never())->method('__invoke'); + assert(is_callable($mock)); return $mock; } - protected function expectCallableNever() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->never()) - ->method('__invoke'); - - return $mock; - } - - protected function createCallableMock() + protected function createCallableMock(): MockObject { $builder = $this->getMockBuilder(\stdClass::class); if (method_exists($builder, 'addMethods')) { From 07bf96b767d64ac142a92858f5bfd79a6ca8caa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 1 Jun 2024 14:48:00 +0200 Subject: [PATCH 75/78] Support `iterable` type for all methods working with multiple keys --- README.md | 13 ++++++----- src/ArrayCache.php | 6 ++--- src/CacheInterface.php | 21 +++++++++--------- tests/ArrayCacheTest.php | 48 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 152277b..50f6b04 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ provide guarantees whether or not the item has been removed from cache. #### getMultiple() -The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to +The `getMultiple(iterable $keys, mixed $default = null): PromiseInterface>` method can be used to retrieve multiple cache items by their unique keys. This method will resolve with an array of cached values on success or with the @@ -142,9 +142,10 @@ Similarly, an expired cache item (once the time-to-live is expired) is considered a cache miss. ```php -$cache->getMultiple(['name', 'age'])->then(function (array $values): void { - $name = $values['name'] ?? 'User'; - $age = $values['age'] ?? 'n/a'; +$cache->getMultiple(['name', 'age'])->then(function (iterable $values): void { + $array = is_array($values) ? $values : iterator_to_array($values); + $name = $array['name'] ?? 'User'; + $age = $array['age'] ?? 'n/a'; echo $name . ' is ' . $age . PHP_EOL; }); @@ -156,7 +157,7 @@ by [promises](https://github.com/reactphp/promise). #### setMultiple() -The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to +The `setMultiple(iterable $values, ?float $ttl = null): PromiseInterface` method can be used to persist a set of key => value pairs in the cache, with an optional TTL. This method will resolve with `true` on success or `false` when an error @@ -178,7 +179,7 @@ and the key `bar` to `2`. If some of the keys already exist, they are overridden #### deleteMultiple() -The `setMultiple(string[] $keys): PromiseInterface` method can be used to +The `setMultiple(iterable $keys): PromiseInterface` method can be used to delete multiple cache items in a single operation. This method will resolve with `true` on success or `false` when an error diff --git a/src/ArrayCache.php b/src/ArrayCache.php index d52bee9..4da7860 100644 --- a/src/ArrayCache.php +++ b/src/ArrayCache.php @@ -123,7 +123,7 @@ public function delete(string $key): PromiseInterface return resolve(true); } - public function getMultiple(array $keys, $default = null): PromiseInterface + public function getMultiple(iterable $keys, $default = null): PromiseInterface { $values = []; @@ -135,7 +135,7 @@ public function getMultiple(array $keys, $default = null): PromiseInterface return all($values); } - public function setMultiple(array $values, ?float $ttl = null): PromiseInterface + public function setMultiple(iterable $values, ?float $ttl = null): PromiseInterface { foreach ($values as $key => $value) { $this->set($key, $value, $ttl); @@ -144,7 +144,7 @@ public function setMultiple(array $values, ?float $ttl = null): PromiseInterface return resolve(true); } - public function deleteMultiple(array $keys): PromiseInterface + public function deleteMultiple(iterable $keys): PromiseInterface { foreach ($keys as $key) { unset($this->data[$key], $this->expires[$key]); diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 278108a..2342eaf 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -106,9 +106,10 @@ public function delete(string $key): PromiseInterface; * considered a cache miss. * * ```php - * $cache->getMultiple(['name', 'age'])->then(function (array $values): void { - * $name = $values['name'] ?? 'User'; - * $age = $values['age'] ?? 'n/a'; + * $cache->getMultiple(['name', 'age'])->then(function (iterable $values): void { + * $array = is_array($values) ? $values : iterator_to_array($values); + * $name = $array['name'] ?? 'User'; + * $age = $array['age'] ?? 'n/a'; * * echo $name . ' is ' . $age . PHP_EOL; * }); @@ -118,11 +119,11 @@ public function delete(string $key): PromiseInterface; * prints some example output. You can use any of the composition provided * by [promises](https://github.com/reactphp/promise). * - * @param string[] $keys A list of keys that can obtained in a single operation. + * @param iterable $keys A list of keys that can obtained in a single operation. * @param mixed $default Default value to return for keys that do not exist. - * @return PromiseInterface> Returns a promise which resolves to an `array` of cached values + * @return PromiseInterface> Returns a promise which resolves to an `array` of cached values */ - public function getMultiple(array $keys, $default = null): PromiseInterface; + public function getMultiple(iterable $keys, $default = null): PromiseInterface; /** * Persists a set of key => value pairs in the cache, with an optional TTL. @@ -144,19 +145,19 @@ public function getMultiple(array $keys, $default = null): PromiseInterface; * This example eventually sets the list of values - the key `foo` to 1 value * and the key `bar` to 2. If some of the keys already exist, they are overridden. * - * @param array $values A list of key => value pairs for a multiple-set operation. + * @param iterable $values A list of key => value pairs for a multiple-set operation. * @param ?float $ttl Optional. The TTL value of this item. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function setMultiple(array $values, ?float $ttl = null): PromiseInterface; + public function setMultiple(iterable $values, ?float $ttl = null): PromiseInterface; /** * Deletes multiple cache items in a single operation. * - * @param string[] $keys A list of string-based keys to be deleted. + * @param iterable $keys A list of string-based keys to be deleted. * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error */ - public function deleteMultiple(array $keys): PromiseInterface; + public function deleteMultiple(iterable $keys): PromiseInterface; /** * Wipes clean the entire cache. diff --git a/tests/ArrayCacheTest.php b/tests/ArrayCacheTest.php index e46c726..d37a570 100644 --- a/tests/ArrayCacheTest.php +++ b/tests/ArrayCacheTest.php @@ -164,6 +164,18 @@ public function testGetMultiple(): void ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => 'baz'])); } + public function testGetMultipleWithIterableKeysFromGenerator(): void + { + $this->cache = new ArrayCache(); + $this->cache->set('foo', '1'); + + $keys = (function (): \Generator { yield from ['foo', 'bar']; })(); + + $this->cache + ->getMultiple($keys, 'baz') + ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => 'baz'])); + } + public function testSetMultiple(): void { $this->cache = new ArrayCache(); @@ -174,6 +186,18 @@ public function testSetMultiple(): void ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => '2'])); } + public function testSetMultipleWithIterableValuesFromGenerator(): void + { + $values = (function(): \Generator { yield from ['foo' => '1', 'bar' => '2']; })(); + + $this->cache = new ArrayCache(); + $this->cache->setMultiple($values, 10); + + $this->cache + ->getMultiple(['foo', 'bar']) + ->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => '2'])); + } + public function testDeleteMultiple(): void { $this->cache = new ArrayCache(); @@ -196,6 +220,30 @@ public function testDeleteMultiple(): void ->then($this->expectCallableOnceWith(false)); } + public function testDeleteMultipleWithIterableKeysFromGenerator(): void + { + $this->cache = new ArrayCache(); + $this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]); + + $keys = (function (): \Generator { yield from ['foo', 'baz']; })(); + + $this->cache + ->deleteMultiple($keys) + ->then($this->expectCallableOnceWith(true)); + + $this->cache + ->has('foo') + ->then($this->expectCallableOnceWith(false)); + + $this->cache + ->has('bar') + ->then($this->expectCallableOnceWith(true)); + + $this->cache + ->has('baz') + ->then($this->expectCallableOnceWith(false)); + } + public function testClearShouldClearCache(): void { $this->cache = new ArrayCache(); From 3e09ad43215d0f7f11097ef61508f8406715ad5f Mon Sep 17 00:00:00 2001 From: Paul Rotmann Date: Tue, 25 Mar 2025 09:32:06 +0100 Subject: [PATCH 76/78] Run tests on PHP 8.4 and update test environment --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 587bd0c..7563ed1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,11 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 @@ -34,10 +35,11 @@ jobs: PHPStan: name: PHPStan (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 From 803bed161b0006f62579fb0003eea5aa82fc5827 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Thu, 16 Oct 2025 12:25:48 +0200 Subject: [PATCH 77/78] [3.x] Run tests on PHP 8.5 Builds on top of #63 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7563ed1..9964f62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.5 - 8.4 - 8.3 - 8.2 @@ -39,6 +40,7 @@ jobs: strategy: matrix: php: + - 8.5 - 8.4 - 8.3 - 8.2 From d2273ef6987ebf61372cd9953d2c60c071dd32a0 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 25 Apr 2026 14:02:12 +0200 Subject: [PATCH 78/78] [3.x] Update test environment for PHP 7.2 to compatible PHPUnit version These changes ensure we can continue to run PHPUnit on PHP 7.2 by updating PHPUnit to 8.5. This is due to recent improvements in composer as discussed in https://github.com/reactphp/event-loop/pull/284. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1cebfcf..ea14955 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ }, "require-dev": { "phpstan/phpstan": "1.11.1 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" + "phpunit/phpunit": "^9.6 || ^8.5 || ^7.5" }, "autoload": { "psr-4": {