From fcf7df788831058284fd137698edfa5752dda2d8 Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Fri, 27 Oct 2017 15:47:13 +0200 Subject: [PATCH 1/6] Use PSR-6 caching interfce & remove desarolla2 cache --- .travis.yml | 7 +- README.md | 51 +- build/logs/clover.xml | 3294 +++++++++++++++++ composer.json | 7 +- config/config.php | 39 - phpunit.xml | 3 - src/Cache/Adapter/CacheAdapterInterface.php | 78 - src/Cache/Adapter/DesarrollaCacheAdapter.php | 89 - ...xception.php => CacheAdapterException.php} | 2 +- src/Cache/Exception/CacheException.php | 4 +- .../Exception/InvalidConfigException.php | 33 - src/Cache/Factory/DesarrollaCacheFactory.php | 212 -- src/Cache/ThrottlerCache.php | 146 + ...erface.php => ThrottlerCacheInterface.php} | 31 +- src/Cache/ThrottlerItemInterface.php | 18 + src/Throttle/Entity/CacheCount.php | 64 + src/Throttle/Entity/CacheHitMapping.php | 64 + src/Throttle/Entity/CacheTime.php | 64 + src/Throttle/Factory/ThrottlerFactory.php | 14 +- .../Factory/TimeAwareThrottlerFactory.php | 14 +- .../Throttler/AbstractWindowThrottler.php | 8 +- .../Throttler/ElasticWindowThrottler.php | 19 +- .../Throttler/FixedWindowThrottler.php | 39 +- .../Throttler/LeakyBucketThrottler.php | 43 +- .../Throttler/MovingWindowThrottler.php | 13 +- .../Adapter/DesarrollaCacheAdapterTest.php | 94 - .../Factory/DesarrollaCacheFactoryTest.php | 48 - tests/Cache/ThrottlerCacheTest.php | 80 + .../Functional/AbstractThrottlerTestCase.php | 21 +- tests/Functional/ElasticWindowTest.php | 9 +- tests/Functional/FixedWindowTest.php | 7 +- tests/Functional/LeakyBucketTest.php | 9 +- tests/Functional/MovingWindowTest.php | 9 +- tests/Functional/RetrialQueueTest.php | 9 +- .../Throttle/Factory/ThrottlerFactoryTest.php | 10 +- .../Factory/TimeAwareThrottlerFactoryTest.php | 10 +- .../Throttler/AbstractWindowThrottlerTest.php | 92 - .../Throttler/ElasticWindowThrottlerTest.php | 84 - .../Throttler/FixedWindowThrottlerTest.php | 106 - .../Throttler/LeakyBucketThrottlerTest.php | 207 -- .../Throttler/MovingWindowThrottlerTest.php | 101 - .../Throttler/RetrialQueueThrottlerTest.php | 104 - 42 files changed, 3914 insertions(+), 1442 deletions(-) create mode 100644 build/logs/clover.xml delete mode 100644 config/config.php delete mode 100644 src/Cache/Adapter/CacheAdapterInterface.php delete mode 100644 src/Cache/Adapter/DesarrollaCacheAdapter.php rename src/Cache/Exception/{DriverNotFoundException.php => CacheAdapterException.php} (95%) delete mode 100644 src/Cache/Exception/InvalidConfigException.php delete mode 100644 src/Cache/Factory/DesarrollaCacheFactory.php create mode 100644 src/Cache/ThrottlerCache.php rename src/Cache/{Factory/FactoryInterface.php => ThrottlerCacheInterface.php} (66%) create mode 100644 src/Cache/ThrottlerItemInterface.php create mode 100644 src/Throttle/Entity/CacheCount.php create mode 100644 src/Throttle/Entity/CacheHitMapping.php create mode 100644 src/Throttle/Entity/CacheTime.php delete mode 100644 tests/Cache/Adapter/DesarrollaCacheAdapterTest.php delete mode 100644 tests/Cache/Factory/DesarrollaCacheFactoryTest.php create mode 100644 tests/Cache/ThrottlerCacheTest.php delete mode 100644 tests/Throttle/Throttler/AbstractWindowThrottlerTest.php delete mode 100644 tests/Throttle/Throttler/ElasticWindowThrottlerTest.php delete mode 100644 tests/Throttle/Throttler/FixedWindowThrottlerTest.php delete mode 100644 tests/Throttle/Throttler/LeakyBucketThrottlerTest.php delete mode 100644 tests/Throttle/Throttler/MovingWindowThrottlerTest.php delete mode 100644 tests/Throttle/Throttler/RetrialQueueThrottlerTest.php diff --git a/.travis.yml b/.travis.yml index 6d8b72f..7362847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,14 @@ language: php php: - - 5.5 - - 5.6 - 7.0 + - 7.1 before_script: - composer self-update - composer install --prefer-source --dev script: - - phpunit --colors --verbose --coverage-clover build/logs/clover.xml + - php vendor/bin/phpunit --colors --verbose --coverage-clover build/logs/clover.xml -c phpunit.xml -after_script: if [ $(phpenv version-name) = "5.6" ]; then php vendor/bin/ocular code-coverage:upload --format=php-clover build/logs/clover.xml; fi +after_script: if [ $(phpenv version-name) = "7.1" ]; then php vendor/bin/ocular code-coverage:upload --format=php-clover build/logs/clover.xml; fi diff --git a/README.md b/README.md index 62f1433..b15ceb6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ by including `sunspikes/php-ratelimiter` in your project composer.json require: ``` json "require": { - "sunspikes/php-ratelimiter": "dev-master" + "sunspikes/php-ratelimiter": "^2.0" } ``` @@ -36,9 +36,9 @@ compatible autoloader. ```php // 1. Make a rate limiter with limit 3 attempts in 10 minutes -$cacheAdapter = new DesarrollaCacheAdapter((new DesarrollaCacheFactory())->make()); +$throttlerCache = new ThrottlerCache($anyPsr6CacheAdapter); $settings = new ElasticWindowSettings(3, 600); -$ratelimiter = new RateLimiter(new ThrottlerFactory($cacheAdapter), new HydratorFactory(), $settings); +$ratelimiter = new RateLimiter(new ThrottlerFactory($throttlerCache), new HydratorFactory(), $settings); // 2. Get a throttler for path /login $loginThrottler = $ratelimiter->get('/login'); @@ -64,41 +64,22 @@ if ($loginThrottler->access()) { print $loginThrottler->count(); // or count($throttler) ``` -### Configuration +### Using and Extending -By default PHP Ratelimiter uses the [desarolla2 cache adapter](https://github.com/desarrolla2/Cache), the sample configuration provided in ```config/config.php``` +The PHP Ratelimiter is highly extensible, you can use any PSR6 cache adapters as caching backend -You can configure the drivers in ```config.php```, for example to use memcache change the driver to ```'memcache'``` +For example, -```php -return [ - 'default_ttl' => 3600, - 'driver' => 'memcache', - 'memcache' => [ - //.... - ], -]; -``` - -### Extending + - PHP Cache (http://www.php-cache.com/en/latest/) + - Symfony (https://symfony.com/doc/current/components/cache.html) + - Stash (http://www.stashphp.com) -The PHP Ratelimiter is highly extensible, you can have custom adapters by implementing ```Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface``` - -For example to use Doctrine cache adapter +For example to use Memcache adapter from php cache ```php -class DoctrineCacheAdapter implements CacheAdapterInterface -{ - public function __construct($cache) - { - $this->cache = $cache; - } - - // Implement the methods -} - -// Build adapter using APC cache driver -$adapter = new DoctrineCacheAdapter(new \Doctrine\Common\Cache\ApcCache()); +$adapter = new \Cache\Adapter\Memcache\MemcacheCachePool(); +$throttlerCache = new ThrottlerCache($adapter); +... ``` Also you can have custom hydrators by implementing ```Sunspikes\Ratelimit\Throttle\Hydrator\DataHydratorInterface``` @@ -157,10 +138,10 @@ See [Overview example](#overview) for instantiation. All the following throttlers use time functions, thus needing a different factory for construction: ```php -$cacheAdapter = new DesarrollaCacheAdapter((new DesarrollaCacheFactory())->make()); +$throttlerCache = new ThrottlerCache($adapter); $timeAdapter = new PhpTimeAdapter(); -$throttlerFactory = new TimeAwareThrottlerFactory($cacheAdapter, $timeAdapter); +$throttlerFactory = new TimeAwareThrottlerFactory($throttlerCache, $timeAdapter); $hydratorFactory = new HydratorFactory(); //$settings = ... @@ -211,6 +192,8 @@ $settings = new RetrialQueueSettings(new LeakyBucketSettings(120, 60000, 120)); Krishnaprasad MG [@sunspikes] +@Feijs + ## Contributing Please feel free to send pull requests. diff --git a/build/logs/clover.xml b/build/logs/clover.xml new file mode 100644 index 0000000..fe8b4a0 --- /dev/null +++ b/build/logs/clover.xml @@ -0,0 +1,3294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index bc2ddd6..91dedf1 100644 --- a/composer.json +++ b/composer.json @@ -10,14 +10,17 @@ } ], "require": { - "php": ">=5.5", - "desarrolla2/cache": "~2.0" + "php": ">=7.0" }, "require-dev": { "mockery/mockery": "^0.9.4", "phpunit/phpunit": "^4.7.6", + "cache/cache": "^0.4", "scrutinizer/ocular": "^1.1" }, + "suggest": { + "cache/cache": "Rate limiter needs one of PSR-6 compatible cache adapters" + }, "autoload": { "psr-4": { "Sunspikes\\Ratelimit\\": "src/" diff --git a/config/config.php b/config/config.php deleted file mode 100644 index c2e392a..0000000 --- a/config/config.php +++ /dev/null @@ -1,39 +0,0 @@ - 3600, - 'driver' => 'notcache', - 'notcache' => [ - // config for not cache - ], - 'file' => [ - 'cache_dir' => './data', - ], - 'apc' => [ - // config for apc - ], - 'memory' => [ - 'limit' => 1000, - ], - 'mongo' => [ - 'server' => 'mongodb://localhost:27017', - ], - 'mysql' => [ - 'host' => '127.0.0.1', - 'username' => 'root', - 'password' => '', - 'dbname' => '', - 'port' => 3306 - ], - 'redis' => [ - // config for redis - ], - 'memcache' => [ - 'servers' => [ - 'localhost' - ] - ], -]; diff --git a/phpunit.xml b/phpunit.xml index 9ab304f..40e023b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -14,7 +14,4 @@ ./tests/ - - - diff --git a/src/Cache/Adapter/CacheAdapterInterface.php b/src/Cache/Adapter/CacheAdapterInterface.php deleted file mode 100644 index 36e1762..0000000 --- a/src/Cache/Adapter/CacheAdapterInterface.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * 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. - */ - -namespace Sunspikes\Ratelimit\Cache\Adapter; - -use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; - -interface CacheAdapterInterface -{ - /** - * Get value from cache - * - * @param string $key - * - * @return mixed - * - * @throws ItemNotFoundException - */ - public function get($key); - - /** - * Set value in cache - * - * @param string $key - * @param mixed $value - * @param int $ttl - * - * @return mixed - */ - public function set($key, $value, $ttl = null); - - /** - * Delete value from cache - * - * @param string $key - * - * @return mixed - */ - public function delete($key); - - /** - * Check if keyed value exists in cache - * - * @param string $key - * - * @return bool - */ - public function has($key); - - /** - * Clear cache - * - * @return void - */ - public function clear(); -} diff --git a/src/Cache/Adapter/DesarrollaCacheAdapter.php b/src/Cache/Adapter/DesarrollaCacheAdapter.php deleted file mode 100644 index 55aadc4..0000000 --- a/src/Cache/Adapter/DesarrollaCacheAdapter.php +++ /dev/null @@ -1,89 +0,0 @@ - - * - * 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. - */ - -namespace Sunspikes\Ratelimit\Cache\Adapter; - -use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; - -/** - * Adapter for the cache library Desarrolla2\Cache - */ -class DesarrollaCacheAdapter implements CacheAdapterInterface -{ - /* @var \Desarrolla2\Cache\CacheInterface $cache */ - protected $cache; - - /** - * @param \Desarrolla2\Cache\CacheInterface $cache - */ - public function __construct($cache) - { - $this->cache = $cache; - } - - /** - * @inheritdoc - */ - public function get($key) - { - if ($this->cache->has($key)) { - return $this->cache->get($key); - } - - throw new ItemNotFoundException('Cannot find the item in cache'); - } - - /** - * @inheritdoc - */ - public function set($key, $value, $ttl = null) - { - $this->cache->set($key, $value, $ttl); - } - - /** - * @inheritdoc - */ - public function delete($key) - { - $this->cache->delete($key); - } - - /** - * @inheritdoc - */ - public function has($key) - { - return $this->cache->has($key); - } - - /** - * @inheritdoc - */ - public function clear() - { - $this->cache->clearCache(); - } -} diff --git a/src/Cache/Exception/DriverNotFoundException.php b/src/Cache/Exception/CacheAdapterException.php similarity index 95% rename from src/Cache/Exception/DriverNotFoundException.php rename to src/Cache/Exception/CacheAdapterException.php index 4a2cc9b..52fb6de 100644 --- a/src/Cache/Exception/DriverNotFoundException.php +++ b/src/Cache/Exception/CacheAdapterException.php @@ -28,6 +28,6 @@ /** * Cache driver not found */ -class DriverNotFoundException extends CacheException +class CacheAdapterException extends CacheException { } diff --git a/src/Cache/Exception/CacheException.php b/src/Cache/Exception/CacheException.php index c4a328f..3a54e4f 100644 --- a/src/Cache/Exception/CacheException.php +++ b/src/Cache/Exception/CacheException.php @@ -25,9 +25,11 @@ namespace Sunspikes\Ratelimit\Cache\Exception; +use Psr\Cache\CacheException as PsrCacheException; + /** * The base cache exception */ -class CacheException extends \Exception +class CacheException extends \RuntimeException implements PsrCacheException { } diff --git a/src/Cache/Exception/InvalidConfigException.php b/src/Cache/Exception/InvalidConfigException.php deleted file mode 100644 index 3840664..0000000 --- a/src/Cache/Exception/InvalidConfigException.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * 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. - */ - -namespace Sunspikes\Ratelimit\Cache\Exception; - -/** - * Invalid driver configuration - */ -class InvalidConfigException extends CacheException -{ -} diff --git a/src/Cache/Factory/DesarrollaCacheFactory.php b/src/Cache/Factory/DesarrollaCacheFactory.php deleted file mode 100644 index ed28969..0000000 --- a/src/Cache/Factory/DesarrollaCacheFactory.php +++ /dev/null @@ -1,212 +0,0 @@ - - * - * 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. - */ - -namespace Sunspikes\Ratelimit\Cache\Factory; - -use Desarrolla2\Cache\Adapter\Apcu; -use Desarrolla2\Cache\Adapter\File; -use Desarrolla2\Cache\Adapter\Memcache; -use Desarrolla2\Cache\Adapter\Memory; -use Desarrolla2\Cache\Adapter\Mongo; -use Desarrolla2\Cache\Adapter\Mysqli; -use Desarrolla2\Cache\Adapter\NotCache; -use Desarrolla2\Cache\Adapter\Predis; -use Desarrolla2\Cache\Cache; -use Sunspikes\Ratelimit\Cache\Exception\DriverNotFoundException; -use Sunspikes\Ratelimit\Cache\Exception\InvalidConfigException; - -class DesarrollaCacheFactory implements FactoryInterface -{ - /* @const DEFAULT_TTL */ - const DEFAULT_TTL = 3600; - /* @const DEFAULT_LIMIT */ - const DEFAULT_LIMIT = 1000; - - /* @var array */ - protected $config; - - /** - * @param string|null $configFile - * @param array $configArray - */ - public function __construct($configFile = null, array $configArray = []) - { - // Default config from distribution - if (null === $configFile) { - $configFile = __DIR__.'/../../../config/config.php'; - } - - $config = include $configFile; - - $this->config = array_merge($config, $configArray); - } - - /** - * @inheritdoc - */ - public function make() - { - return new Cache($this->getDriver()); - } - - /** - * Make the driver based on given config - * - * @return null|\Desarrolla2\Cache\Adapter\AdapterInterface - * - * @throws DriverNotFoundException - * @throws InvalidConfigException - */ - protected function getDriver() - { - $driver = $this->config['driver']; - - if (is_null($driver)) { - throw new InvalidConfigException('Cache driver is not defined in configuration.'); - } - - $driverCreateMethod = 'create' . ucfirst($driver) . 'Driver'; - - if (method_exists($this, $driverCreateMethod)) { - $driver = $this->{$driverCreateMethod}(); - $driver->setOption('ttl', - $this->config['default_ttl'] - ?: static::DEFAULT_TTL - ); - - return $driver; - } - - throw new DriverNotFoundException('Cannot find the driver ' . $driver . ' for Desarrolla'); - } - - /** - * Create NotCache driver - * - * @return NotCache - */ - protected function createNotcacheDriver() - { - return new NotCache(); - } - - /** - * Create File driver - * - * @return File - */ - protected function createFileDriver() - { - return new File($this->config['file']['cache_dir']); - } - - /** - * Create APC driver - * - * @return Apcu - */ - protected function createApcDriver() - { - return new Apcu(); - } - - /** - * Create Memory driver - * - * @return Memory - */ - protected function createMemoryDriver() - { - $memory = new Memory(); - $memory->setOption('limit', - $this->config['memory']['limit'] - ?: static::DEFAULT_LIMIT - ); - - return $memory; - } - - /** - * Create Mongo driver - * - * @return Mongo - */ - protected function createMongoDriver() - { - return new Mongo($this->config['mongo']['server']); - } - - /** - * Create MySQL driver - * - * @return Mysqli - */ - protected function createMysqlDriver() - { - $server = null; - - if (!empty($this->config['mysql'])) { - $server = new \mysqli( - $this->config['mysql']['host'], - $this->config['mysql']['username'], - $this->config['mysql']['password'], - $this->config['mysql']['dbname'], - $this->config['mysql']['port'] - ); - } - - return new Mysqli($server); - } - - /** - * Create Redis driver - * - * @return Predis - */ - protected function createRedisDriver() - { - return new Predis(); - } - - /** - * Create MemCache driver - * - * @return MemCache - */ - protected function createMemcacheDriver() - { - $server = null; - - if (isset($this->config['memcache']['servers'])) { - $server = new \Memcache(); - - foreach ($this->config['memcache']['servers'] as $host) { - $server->addserver($host); - } - } - - return new Memcache($server); - } -} diff --git a/src/Cache/ThrottlerCache.php b/src/Cache/ThrottlerCache.php new file mode 100644 index 0000000..a6cd2ba --- /dev/null +++ b/src/Cache/ThrottlerCache.php @@ -0,0 +1,146 @@ + + * + * 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. + */ + +namespace Sunspikes\Ratelimit\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\CacheException as PsrCacheException; +use Sunspikes\Ratelimit\Cache\Exception\CacheAdapterException; +use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; + +class ThrottlerCache implements ThrottlerCacheInterface +{ + /** + * @var CacheItemPoolInterface + */ + private $cacheItemPool; + + /** + * ThrottlerCache constructor. + * + * @param CacheItemPoolInterface $cacheItemPool + */ + public function __construct(CacheItemPoolInterface $cacheItemPool) + { + $this->cacheItemPool = $cacheItemPool; + } + + /** + * @param string $key + * + * @return ThrottlerItemInterface + */ + public function getItem(string $key): ThrottlerItemInterface + { + $item = $this->getThrottlerItem($key); + + return $item; + } + + /** + * @param string $key + * + * @return bool + */ + public function hasItem(string $key): bool + { + return $this->cacheItemPool->hasItem($key); + } + + /** + * @param string $key + * @param mixed $item + * @return bool + */ + public function setItem(string $key, ThrottlerItemInterface $item): bool + { + return $this->setThrottlerItem($key, $item); + } + + /** + * @param string $key + */ + public function removeItem(string $key) + { + $this->cacheItemPool->deleteItem($key); + } + + /** + * @param string $key + * + * @return ThrottlerItemInterface + * @throws CacheAdapterException + * @throws ItemNotFoundException + */ + private function getThrottlerItem(string $key): ThrottlerItemInterface + { + try { + $item = $this->cacheItemPool->getItem($key); + + if ($item->isHit()) { + $params = json_decode($item->get(), true); + $class = $params['class']; + + return $class::createFromArray($params['data']); + } + } catch (PsrCacheException $e) { + } + + throw new ItemNotFoundException('Item not found.'); + } + + /** + * @param string $key + * @param ThrottlerItemInterface $item + * + * @return bool + * @throws CacheAdapterException + */ + private function setThrottlerItem(string $key, ThrottlerItemInterface $item) + { + try { + $cacheItem = $this->cacheItemPool->getItem($key); + + if ($cacheItem->isHit()) { + $this->cacheItemPool->deleteItem($key); + } + } catch (PsrCacheException $e) { + throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); + } + + $cacheItem->set( + json_encode( + [ + 'class' => get_class($item), + 'data' => $item, + ] + ) + ); + + $cacheItem->expiresAfter($item->getTtl()); + + return $this->cacheItemPool->save($cacheItem); + } +} \ No newline at end of file diff --git a/src/Cache/Factory/FactoryInterface.php b/src/Cache/ThrottlerCacheInterface.php similarity index 66% rename from src/Cache/Factory/FactoryInterface.php rename to src/Cache/ThrottlerCacheInterface.php index 5f5aad3..e6a3308 100644 --- a/src/Cache/Factory/FactoryInterface.php +++ b/src/Cache/ThrottlerCacheInterface.php @@ -23,14 +23,33 @@ * SOFTWARE. */ -namespace Sunspikes\Ratelimit\Cache\Factory; +namespace Sunspikes\Ratelimit\Cache; -interface FactoryInterface +interface ThrottlerCacheInterface { /** - * Create a cache driver adapter + * @param string $key * - * @return mixed + * @return ThrottlerItemInterface */ - public function make(); -} + public function getItem(string $key): ThrottlerItemInterface; + + /** + * @param string $key + * @param ThrottlerItemInterface $item + * @return bool + */ + public function setItem(string $key, ThrottlerItemInterface $item): bool; + + /** + * @param string $key + * + * @return bool + */ + public function hasItem(string $key): bool; + + /** + * @param string $key + */ + public function removeItem(string $key); +} \ No newline at end of file diff --git a/src/Cache/ThrottlerItemInterface.php b/src/Cache/ThrottlerItemInterface.php new file mode 100644 index 0000000..aa46f36 --- /dev/null +++ b/src/Cache/ThrottlerItemInterface.php @@ -0,0 +1,18 @@ +count = $count; + $this->ttl = $ttl; + } + + /** + * @param array $array + * + * @return ThrottlerItemInterface + */ + public static function createFromArray(array $array): ThrottlerItemInterface + { + return new static( + $array['count'], + $array['ttl'] + ); + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'count' => $this->count, + 'ttl' => $this->ttl, + ]; + } + + /** + * @return int + */ + public function getCount(): int + { + return $this->count; + } + + /** + * @return int|null + */ + public function getTtl() + { + return $this->ttl; + } +} \ No newline at end of file diff --git a/src/Throttle/Entity/CacheHitMapping.php b/src/Throttle/Entity/CacheHitMapping.php new file mode 100644 index 0000000..cba47a2 --- /dev/null +++ b/src/Throttle/Entity/CacheHitMapping.php @@ -0,0 +1,64 @@ +hitMapping = $hitMapping; + $this->ttl = $ttl; + } + + /** + * @param array $array + * + * @return ThrottlerItemInterface + */ + public static function createFromArray(array $array): ThrottlerItemInterface + { + return new static( + $array['hitMapping'], + $array['ttl'] + ); + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'hitMapping' => $this->hitMapping, + 'ttl' => $this->ttl, + ]; + } + + /** + * @return array + */ + public function getHitMapping(): array + { + return $this->hitMapping; + } + + /** + * @return int|null + */ + public function getTtl() + { + return $this->ttl; + } +} \ No newline at end of file diff --git a/src/Throttle/Entity/CacheTime.php b/src/Throttle/Entity/CacheTime.php new file mode 100644 index 0000000..50f0a56 --- /dev/null +++ b/src/Throttle/Entity/CacheTime.php @@ -0,0 +1,64 @@ +time = $time; + $this->ttl = $ttl; + } + + /** + * @param array $array + * + * @return ThrottlerItemInterface + */ + public static function createFromArray(array $array): ThrottlerItemInterface + { + return new static( + $array['time'], + $array['ttl'] + ); + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'time' => $this->time, + 'ttl' => $this->ttl, + ]; + } + + /** + * @return float + */ + public function getTime(): float + { + return $this->time; + } + + /** + * @return float|null + */ + public function getTtl() + { + return $this->ttl; + } +} \ No newline at end of file diff --git a/src/Throttle/Factory/ThrottlerFactory.php b/src/Throttle/Factory/ThrottlerFactory.php index 66187ba..06692cb 100644 --- a/src/Throttle/Factory/ThrottlerFactory.php +++ b/src/Throttle/Factory/ThrottlerFactory.php @@ -25,7 +25,7 @@ namespace Sunspikes\Ratelimit\Throttle\Factory; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Settings\ElasticWindowSettings; use Sunspikes\Ratelimit\Throttle\Settings\ThrottleSettingsInterface; @@ -34,16 +34,16 @@ class ThrottlerFactory implements FactoryInterface { /** - * @var CacheAdapterInterface + * @var ThrottlerCacheInterface */ - protected $cacheAdapter; + protected $throttlerCache; /** - * @param CacheAdapterInterface $cacheAdapter + * @param ThrottlerCacheInterface $throttlerCache */ - public function __construct(CacheAdapterInterface $cacheAdapter) + public function __construct(ThrottlerCacheInterface $throttlerCache) { - $this->cacheAdapter = $cacheAdapter; + $this->throttlerCache = $throttlerCache; } /** @@ -68,7 +68,7 @@ protected function createThrottler(Data $data, ThrottleSettingsInterface $settin { if ($settings instanceof ElasticWindowSettings) { return new ElasticWindowThrottler( - $this->cacheAdapter, + $this->throttlerCache, $data->getKey(), $settings->getLimit(), $settings->getTime() diff --git a/src/Throttle/Factory/TimeAwareThrottlerFactory.php b/src/Throttle/Factory/TimeAwareThrottlerFactory.php index 1b9f29e..ea04a42 100644 --- a/src/Throttle/Factory/TimeAwareThrottlerFactory.php +++ b/src/Throttle/Factory/TimeAwareThrottlerFactory.php @@ -25,7 +25,7 @@ namespace Sunspikes\Ratelimit\Throttle\Factory; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Settings\FixedWindowSettings; use Sunspikes\Ratelimit\Throttle\Settings\LeakyBucketSettings; @@ -47,12 +47,12 @@ class TimeAwareThrottlerFactory extends ThrottlerFactory private $timeAdapter; /** - * @param CacheAdapterInterface $cacheAdapter + * @param ThrottlerCacheInterface $throttlerCache * @param TimeAdapterInterface $timeAdapter */ - public function __construct(CacheAdapterInterface $cacheAdapter, TimeAdapterInterface $timeAdapter) + public function __construct(ThrottlerCacheInterface $throttlerCache, TimeAdapterInterface $timeAdapter) { - parent::__construct($cacheAdapter); + parent::__construct($throttlerCache); $this->timeAdapter = $timeAdapter; } @@ -81,7 +81,7 @@ private function createNestableController(Data $data, ThrottleSettingsInterface { if ($settings instanceof LeakyBucketSettings) { return new LeakyBucketThrottler( - $this->cacheAdapter, + $this->throttlerCache, $this->timeAdapter, $data->getKey(), $settings->getTokenLimit(), @@ -93,7 +93,7 @@ private function createNestableController(Data $data, ThrottleSettingsInterface if ($settings instanceof MovingWindowSettings) { return new MovingWindowThrottler( - $this->cacheAdapter, + $this->throttlerCache, $this->timeAdapter, $data->getKey(), $settings->getHitLimit(), @@ -104,7 +104,7 @@ private function createNestableController(Data $data, ThrottleSettingsInterface if ($settings instanceof FixedWindowSettings) { return new FixedWindowThrottler( - $this->cacheAdapter, + $this->throttlerCache, $this->timeAdapter, $data->getKey(), $settings->getHitLimit(), diff --git a/src/Throttle/Throttler/AbstractWindowThrottler.php b/src/Throttle/Throttler/AbstractWindowThrottler.php index 44ddd91..1a5fa91 100644 --- a/src/Throttle/Throttler/AbstractWindowThrottler.php +++ b/src/Throttle/Throttler/AbstractWindowThrottler.php @@ -25,13 +25,13 @@ namespace Sunspikes\Ratelimit\Throttle\Throttler; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Time\TimeAdapterInterface; abstract class AbstractWindowThrottler { /** - * @var CacheAdapterInterface + * @var ThrottlerCacheInterface */ protected $cache; @@ -61,7 +61,7 @@ abstract class AbstractWindowThrottler protected $timeProvider; /** - * @param CacheAdapterInterface $cache + * @param ThrottlerCacheInterface $cache * @param TimeAdapterInterface $timeAdapter * @param string $key Cache key prefix * @param int $hitLimit Maximum number of hits @@ -69,7 +69,7 @@ abstract class AbstractWindowThrottler * @param int|null $cacheTtl Cache ttl time (default: null => CacheAdapter ttl) */ public function __construct( - CacheAdapterInterface $cache, + ThrottlerCacheInterface $cache, TimeAdapterInterface $timeAdapter, $key, $hitLimit, diff --git a/src/Throttle/Throttler/ElasticWindowThrottler.php b/src/Throttle/Throttler/ElasticWindowThrottler.php index e7f40af..38a5949 100644 --- a/src/Throttle/Throttler/ElasticWindowThrottler.php +++ b/src/Throttle/Throttler/ElasticWindowThrottler.php @@ -25,12 +25,13 @@ namespace Sunspikes\Ratelimit\Throttle\Throttler; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; +use Sunspikes\Ratelimit\Throttle\Entity\CacheCount; class ElasticWindowThrottler implements RetriableThrottlerInterface, \Countable { - /* @var CacheAdapterInterface */ + /* @var ThrottlerCacheInterface */ protected $cache; /* @var string */ protected $key; @@ -42,12 +43,12 @@ class ElasticWindowThrottler implements RetriableThrottlerInterface, \Countable protected $counter; /** - * @param CacheAdapterInterface $cache + * @param ThrottlerCacheInterface $cache * @param string $key * @param int $limit * @param int $ttl */ - public function __construct(CacheAdapterInterface $cache, $key, $limit, $ttl) + public function __construct(ThrottlerCacheInterface $cache, $key, $limit, $ttl) { $this->cache = $cache; $this->key = $key; @@ -73,8 +74,9 @@ public function access() public function hit() { $this->counter = $this->count() + 1; + $item = new CacheCount($this->counter, $this->ttl); - $this->cache->set($this->key, $this->counter, $this->ttl); + $this->cache->setItem($this->key, $item); return $this; } @@ -85,8 +87,9 @@ public function hit() public function clear() { $this->counter = 0; + $item = new CacheCount($this->counter, $this->ttl); - $this->cache->set($this->key, $this->counter, $this->ttl); + $this->cache->setItem($this->key, $item); return $this; } @@ -101,7 +104,9 @@ public function count() } try { - $this->counter = $this->cache->get($this->key); + /** @var CacheCount $item */ + $item = $this->cache->getItem($this->key); + $this->counter = $item->getCount(); } catch (ItemNotFoundException $e) { $this->counter = 0; } diff --git a/src/Throttle/Throttler/FixedWindowThrottler.php b/src/Throttle/Throttler/FixedWindowThrottler.php index 1ee3c8a..6e31913 100644 --- a/src/Throttle/Throttler/FixedWindowThrottler.php +++ b/src/Throttle/Throttler/FixedWindowThrottler.php @@ -26,11 +26,13 @@ namespace Sunspikes\Ratelimit\Throttle\Throttler; use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; +use Sunspikes\Ratelimit\Throttle\Entity\CacheTime; +use Sunspikes\Ratelimit\Throttle\Entity\CacheCount; final class FixedWindowThrottler extends AbstractWindowThrottler implements RetriableThrottlerInterface { - const CACHE_KEY_TIME = ':time'; - const CACHE_KEY_HITS = ':hits'; + const CACHE_KEY_TIME = '-time'; + const CACHE_KEY_HITS = '-hits'; /** * @var int|null @@ -43,14 +45,16 @@ final class FixedWindowThrottler extends AbstractWindowThrottler implements Retr public function hit() { $this->setCachedHitCount($this->count() + 1); - + $item = new CacheTime($this->timeProvider->now(), $this->cacheTtl); // Update the window start time if the previous window has passed, or no cached window exists try { - if (($this->timeProvider->now() - $this->cache->get($this->getTimeCacheKey())) > $this->timeLimit) { - $this->cache->set($this->getTimeCacheKey(), $this->timeProvider->now(), $this->cacheTtl); + /** @var CacheTime $currentItem */ + $currentItem = $this->cache->getItem($this->getTimeCacheKey()); + if (($this->timeProvider->now() - $currentItem->getTime()) > $this->timeLimit) { + $this->cache->setItem($this->getTimeCacheKey(), $item); } } catch (ItemNotFoundException $exception) { - $this->cache->set($this->getTimeCacheKey(), $this->timeProvider->now(), $this->cacheTtl); + $this->cache->setItem($this->getTimeCacheKey(), $item); } return $this; @@ -62,7 +66,9 @@ public function hit() public function count() { try { - if (($this->timeProvider->now() - $this->cache->get($this->getTimeCacheKey())) > $this->timeLimit) { + /** @var CacheTime $currentItem */ + $currentItem = $this->cache->getItem($this->getTimeCacheKey()); + if (($this->timeProvider->now() - $currentItem->getTime()) > $this->timeLimit) { return 0; } @@ -78,7 +84,8 @@ public function count() public function clear() { $this->setCachedHitCount(0); - $this->cache->set($this->getTimeCacheKey(), $this->timeProvider->now(), $this->cacheTtl); + $item = new CacheTime($this->timeProvider->now(), $this->cacheTtl); + $this->cache->setItem($this->getTimeCacheKey(), $item); } /** @@ -92,9 +99,10 @@ public function getRetryTimeout() // Return the time until the current window ends // Try/catch for the ItemNotFoundException is not required, in that case $this->check() will return true - $cachedTime = $this->cache->get($this->getTimeCacheKey()); + /** @var CacheTime $cachedTime */ + $cachedTime = $this->cache->getItem($this->getTimeCacheKey()); - return self::SECOND_TO_MILLISECOND_MULTIPLIER * ($this->timeLimit - $this->timeProvider->now() + $cachedTime); + return self::SECOND_TO_MILLISECOND_MULTIPLIER * ($this->timeLimit - $this->timeProvider->now() + $cachedTime->getTime()); } /** @@ -107,8 +115,10 @@ private function getCachedHitCount() if (null !== $this->hitCount) { return $this->hitCount; } + /** @var CacheCount $item */ + $item = $this->cache->getItem($this->getHitsCacheKey()); - return $this->cache->get($this->getHitsCacheKey()); + return $item->getCount(); } /** @@ -117,7 +127,8 @@ private function getCachedHitCount() private function setCachedHitCount($hitCount) { $this->hitCount = $hitCount; - $this->cache->set($this->getHitsCacheKey(), $hitCount, $this->cacheTtl); + $item = new CacheCount($hitCount, $this->cacheTtl); + $this->cache->setItem($this->getHitsCacheKey(), $item); } /** @@ -125,7 +136,7 @@ private function setCachedHitCount($hitCount) */ private function getHitsCacheKey() { - return $this->key.self::CACHE_KEY_HITS; + return $this->key . self::CACHE_KEY_HITS; } /** @@ -133,6 +144,6 @@ private function getHitsCacheKey() */ private function getTimeCacheKey() { - return $this->key.self::CACHE_KEY_TIME; + return $this->key . self::CACHE_KEY_TIME; } } diff --git a/src/Throttle/Throttler/LeakyBucketThrottler.php b/src/Throttle/Throttler/LeakyBucketThrottler.php index 4951bd6..b827706 100644 --- a/src/Throttle/Throttler/LeakyBucketThrottler.php +++ b/src/Throttle/Throttler/LeakyBucketThrottler.php @@ -26,16 +26,18 @@ namespace Sunspikes\Ratelimit\Throttle\Throttler; use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; +use Sunspikes\Ratelimit\Throttle\Entity\CacheCount; +use Sunspikes\Ratelimit\Throttle\Entity\CacheTime; use Sunspikes\Ratelimit\Time\TimeAdapterInterface; final class LeakyBucketThrottler implements RetriableThrottlerInterface { - const CACHE_KEY_TIME = ':time'; - const CACHE_KEY_TOKEN = ':tokens'; + const CACHE_KEY_TIME = '-time'; + const CACHE_KEY_TOKEN = '-tokens'; /** - * @var CacheAdapterInterface + * @var ThrottlerCacheInterface */ private $cache; @@ -70,16 +72,16 @@ final class LeakyBucketThrottler implements RetriableThrottlerInterface private $tokenlimit; /** - * @param CacheAdapterInterface $cache - * @param TimeAdapterInterface $timeAdapter - * @param string $key Cache key prefix - * @param int $tokenLimit Bucket capacity - * @param int $timeLimit Refill time in milliseconds - * @param int|null $threshold Capacity threshold on which to start throttling (default: 0) - * @param int|null $cacheTtl Cache ttl time (default: null => CacheAdapter ttl) + * @param ThrottlerCacheInterface $cache + * @param TimeAdapterInterface $timeAdapter + * @param string $key Cache key prefix + * @param int $tokenLimit Bucket capacity + * @param int $timeLimit Refill time in milliseconds + * @param int|null $threshold Capacity threshold on which to start throttling (default: 0) + * @param int|null $cacheTtl Cache ttl time (default: null => CacheAdapter ttl) */ public function __construct( - CacheAdapterInterface $cache, + ThrottlerCacheInterface $cache, TimeAdapterInterface $timeAdapter, $key, $tokenLimit, @@ -134,14 +136,16 @@ public function clear() public function count() { try { - $cachedTime = $this->cache->get($this->getTimeCacheKey()); - $timeSinceLastRequest = self::SECOND_TO_MILLISECOND_MULTIPLIER * ($this->timeProvider->now() - $cachedTime); + /** @var CacheTime $timeItem */ + $timeItem = $this->cache->getItem($this->getTimeCacheKey()); + $timeSinceLastRequest = self::SECOND_TO_MILLISECOND_MULTIPLIER * ($this->timeProvider->now() - $timeItem->getTime()); if ($timeSinceLastRequest > $this->timeLimit) { return 0; } - $lastTokenCount = $this->cache->get($this->getTokenCacheKey()); + /** @var CacheCount $countItem */ + $countItem = $this->cache->getItem($this->getTokenCacheKey()); } catch (ItemNotFoundException $exception) { $this->clear(); //Clear the bucket @@ -149,7 +153,7 @@ public function count() } // Return the `used` token count, minus the amount of tokens which have been `refilled` since the previous request - return (int) max(0, ceil($lastTokenCount - ($this->tokenlimit * $timeSinceLastRequest / ($this->timeLimit)))); + return (int) max(0, ceil($countItem->getCount() - ($this->tokenlimit * $timeSinceLastRequest / ($this->timeLimit)))); } /** @@ -207,8 +211,11 @@ private function getWaitTime($tokenCount) */ private function setUsedCapacity($tokens) { - $this->cache->set($this->getTokenCacheKey(), $tokens, $this->cacheTtl); - $this->cache->set($this->getTimeCacheKey(), $this->timeProvider->now(), $this->cacheTtl); + $countItem = new CacheCount($tokens, $this->cacheTtl); + $this->cache->setItem($this->getTokenCacheKey(), $countItem); + + $timeItem = new CacheTime($this->timeProvider->now(), $this->cacheTtl); + $this->cache->setItem($this->getTimeCacheKey(), $timeItem); } /** diff --git a/src/Throttle/Throttler/MovingWindowThrottler.php b/src/Throttle/Throttler/MovingWindowThrottler.php index 276b062..ce57236 100644 --- a/src/Throttle/Throttler/MovingWindowThrottler.php +++ b/src/Throttle/Throttler/MovingWindowThrottler.php @@ -26,6 +26,7 @@ namespace Sunspikes\Ratelimit\Throttle\Throttler; use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; +use Sunspikes\Ratelimit\Throttle\Entity\CacheHitMapping; final class MovingWindowThrottler extends AbstractWindowThrottler implements RetriableThrottlerInterface { @@ -50,7 +51,9 @@ public function hit() //Adds 1 recorded hit to the mapping entry for the current timestamp $this->hitCountMapping[$timestamp]++; - $this->cache->set($this->key, serialize($this->hitCountMapping), $this->cacheTtl); + + $item = new CacheHitMapping($this->hitCountMapping, $this->cacheTtl); + $this->cache->setItem($this->key, $item); return $this; } @@ -94,7 +97,9 @@ public function getRetryTimeout() public function clear() { $this->hitCountMapping = []; - $this->cache->set($this->key, serialize([]), $this->cacheTtl); + + $item = new CacheHitMapping($this->hitCountMapping, $this->cacheTtl); + $this->cache->setItem($this->key, $item); } private function updateHitCount() @@ -102,7 +107,9 @@ private function updateHitCount() try { // Get a stored mapping from cache if (0 === count($this->hitCountMapping)) { - $this->hitCountMapping = (array) unserialize($this->cache->get($this->key)); + /** @var CacheHitMapping $item */ + $item = $this->cache->getItem($this->key); + $this->hitCountMapping = $item->getHitMapping(); } } catch (ItemNotFoundException $exception) {} diff --git a/tests/Cache/Adapter/DesarrollaCacheAdapterTest.php b/tests/Cache/Adapter/DesarrollaCacheAdapterTest.php deleted file mode 100644 index d899dea..0000000 --- a/tests/Cache/Adapter/DesarrollaCacheAdapterTest.php +++ /dev/null @@ -1,94 +0,0 @@ -shouldReceive('set') - ->with('key', 'value', 30) - ->andReturnNull(); - - $cache->shouldReceive('get') - ->with('key') - ->andReturn('value'); - - $cache->shouldReceive('get') - ->with('non-existing-key') - ->andThrow(new ItemNotFoundException()); - - $cache->shouldReceive('has') - ->with('non-existing-key') - ->andReturn(false); - - $cache->shouldReceive('has') - ->with('key') - ->andReturn(true); - - $cache->shouldReceive('has') - ->with('has-existing-key') - ->andReturn(true); - - $cache->shouldReceive('has') - ->with('has-nonexisting-key') - ->andReturn(false); - - $cache->shouldReceive('delete') - ->with('delete-key') - ->andReturnNull(); - - $cache->shouldReceive('clearCache') - ->withNoArgs() - ->andReturnNull(); - - $this->adapterMock = new DesarrollaCacheAdapter($cache); - } - - public function testSet() - { - $this->assertNull($this->adapterMock->set('key', 'value', 30)); - } - - public function testGet() - { - $this->assertEquals('value', $this->adapterMock->get('key')); - } - - /** - * @expectedException \Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException - */ - public function testGetNonExisting() - { - $this->adapterMock->get('non-existing-key'); - } - - public function testHasExisting() - { - $this->assertTrue($this->adapterMock->has('has-existing-key')); - } - - public function testHasNonExisting() - { - $this->assertFalse($this->adapterMock->has('has-nonexisting-key')); - } - - public function testDelete() - { - $this->assertNull($this->adapterMock->delete('delete-key')); - } - - public function testClear() - { - $this->assertNull($this->adapterMock->clear()); - } -} diff --git a/tests/Cache/Factory/DesarrollaCacheFactoryTest.php b/tests/Cache/Factory/DesarrollaCacheFactoryTest.php deleted file mode 100644 index 6386155..0000000 --- a/tests/Cache/Factory/DesarrollaCacheFactoryTest.php +++ /dev/null @@ -1,48 +0,0 @@ -make(); - - $this->assertInstanceOf(Cache::class, $cache); - } - - /** - * @dataProvider configProvider - * - * @param array $config - */ - public function testCreateDrivers(array $config, $driverClass) - { - if (null !== $driverClass && !class_exists($driverClass)) { - $this->markTestSkipped($driverClass.' is not available on this system'); - } - - $factory = new DesarrollaCacheFactory(null, $config); - $this->assertInstanceOf(Cache::class, $factory->make()); - } - - /** - * @return array - */ - public function configProvider() - { - return [ - [['driver' => 'file'], null], - [['driver' => 'apc'], null], - [['driver' => 'memory'], null], - [['driver' => 'mongo'], \MongoClient::class], - [['driver' => 'redis'], \Predis\Client::class], - [['driver' => 'mysql', 'mysql' => []], \mysqli::class], - [['driver' => 'memcache'], \Memcache::class], - ]; - } -} diff --git a/tests/Cache/ThrottlerCacheTest.php b/tests/Cache/ThrottlerCacheTest.php new file mode 100644 index 0000000..b438b91 --- /dev/null +++ b/tests/Cache/ThrottlerCacheTest.php @@ -0,0 +1,80 @@ +set(json_encode(['class' => CacheCount::class, 'data' => $countItem])); + + $cacheItemPool = M::mock(CacheItemPoolInterface::class); + $cacheItemPool->shouldReceive('getItem') + ->with($key) + ->andReturn($cacheItem); + + $cache = new ThrottlerCache($cacheItemPool); + $gotItem = $cache->getItem($key); + + $this->assertEquals($countItem, $gotItem); + } + + /** + * @expectedException \Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException + */ + public function testGetItemNotFound() + { + $key = 'key'; + $cacheItemPool = M::mock(CacheItemPoolInterface::class); + $cacheItemPool->shouldReceive('getItem') + ->with($key) + ->andThrow(CachePoolException::class); + + $cache = new ThrottlerCache($cacheItemPool); + $cache->getItem($key); + } + + public function testHasItem() + { + $key = 'key'; + $cacheItemPool = M::mock(CacheItemPoolInterface::class); + $cacheItemPool->shouldReceive('hasItem') + ->with($key) + ->andReturn(true); + + $cache = new ThrottlerCache($cacheItemPool); + $hasItem = $cache->hasItem($key); + + $this->assertEquals(true, $hasItem); + } + + public function testSetItem() + { + $key = 'key'; + $countItem = new CacheCount(1); + + $cacheItemPool = M::mock(CacheItemPoolInterface::class); + $cacheItemPool->shouldReceive('getItem') + ->with($key) + ->andReturn(new CacheItem($key)); + $cacheItemPool->shouldReceive('save') + ->with(M::type(CacheItem::class)) + ->andReturn(true); + + $cache = new ThrottlerCache($cacheItemPool); + $status = $cache->setItem($key, $countItem); + + $this->assertEquals(true, $status); + } +} \ No newline at end of file diff --git a/tests/Functional/AbstractThrottlerTestCase.php b/tests/Functional/AbstractThrottlerTestCase.php index a8cedb9..5c87f38 100644 --- a/tests/Functional/AbstractThrottlerTestCase.php +++ b/tests/Functional/AbstractThrottlerTestCase.php @@ -1,10 +1,10 @@ 'memory', - 'memory' => ['limit' => 10], - ]); + $pool = new ArrayCachePool(); + $cache = new ThrottlerCache($pool); - $this->ratelimiter = $this->createRatelimiter($cacheFactory); + $this->ratelimiter = $this->createRatelimiter($cache); } public function testThrottlePreLimit() @@ -46,6 +44,7 @@ public function testThrottlePostLimit() $throttle->hit(); } + $this->assertFalse($throttle->check()); } @@ -89,9 +88,9 @@ protected function getMaxAttempts() } /** - * @param FactoryInterface $cacheFactory + * @param ThrottlerCacheInterface $throttlerCache * * @return RateLimiter */ - abstract protected function createRatelimiter(FactoryInterface $cacheFactory); + abstract protected function createRatelimiter(ThrottlerCacheInterface $throttlerCache); } diff --git a/tests/Functional/ElasticWindowTest.php b/tests/Functional/ElasticWindowTest.php index 3912139..c1a51d0 100644 --- a/tests/Functional/ElasticWindowTest.php +++ b/tests/Functional/ElasticWindowTest.php @@ -1,9 +1,8 @@ make())), + new ThrottlerFactory($throttlerCache), new HydratorFactory(), new ElasticWindowSettings($this->getMaxAttempts(), 600) ); diff --git a/tests/Functional/FixedWindowTest.php b/tests/Functional/FixedWindowTest.php index 2f31940..0e6c41f 100644 --- a/tests/Functional/FixedWindowTest.php +++ b/tests/Functional/FixedWindowTest.php @@ -1,10 +1,11 @@ make()), $this->timeAdapter), + new TimeAwareThrottlerFactory($throttlerCache, $this->timeAdapter), new HydratorFactory(), new FixedWindowSettings($this->getMaxAttempts(), self::TIME_LIMIT) ); diff --git a/tests/Functional/LeakyBucketTest.php b/tests/Functional/LeakyBucketTest.php index 67fbc7a..13c43b6 100644 --- a/tests/Functional/LeakyBucketTest.php +++ b/tests/Functional/LeakyBucketTest.php @@ -1,10 +1,9 @@ make()), $this->timeAdapter), + new TimeAwareThrottlerFactory($throttlerCache, $this->timeAdapter), new HydratorFactory(), new LeakyBucketSettings(self::TOKEN_LIMIT, self::TIME_LIMIT, $this->getMaxAttempts()) ); diff --git a/tests/Functional/MovingWindowTest.php b/tests/Functional/MovingWindowTest.php index b5f6bbb..3d80cde 100644 --- a/tests/Functional/MovingWindowTest.php +++ b/tests/Functional/MovingWindowTest.php @@ -1,10 +1,9 @@ make()), $this->timeAdapter), + new TimeAwareThrottlerFactory($throttlerCache, $this->timeAdapter), new HydratorFactory(), new MovingWindowSettings($this->getMaxAttempts(), self::TIME_LIMIT) ); diff --git a/tests/Functional/RetrialQueueTest.php b/tests/Functional/RetrialQueueTest.php index 3265a81..32b01b5 100644 --- a/tests/Functional/RetrialQueueTest.php +++ b/tests/Functional/RetrialQueueTest.php @@ -1,10 +1,9 @@ make()), $this->timeAdapter), + new TimeAwareThrottlerFactory($throttlerCache, $this->timeAdapter), new HydratorFactory(), new RetrialQueueSettings(new FixedWindowSettings($this->getMaxAttempts(), self::TIME_LIMIT)) ); diff --git a/tests/Throttle/Factory/ThrottlerFactoryTest.php b/tests/Throttle/Factory/ThrottlerFactoryTest.php index ff7b432..75290ca 100644 --- a/tests/Throttle/Factory/ThrottlerFactoryTest.php +++ b/tests/Throttle/Factory/ThrottlerFactoryTest.php @@ -4,7 +4,7 @@ use Mockery as M; use Mockery\MockInterface; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Factory\ThrottlerFactory; use Sunspikes\Ratelimit\Throttle\Settings\ElasticWindowSettings; @@ -15,9 +15,9 @@ class ThrottlerFactoryTest extends \PHPUnit_Framework_TestCase { /** - * @var CacheAdapterInterface|MockInterface + * @var ThrottlerCacheInterface|MockInterface */ - protected $cacheAdapter; + protected $throttlerCache; /** * @var FactoryInterface @@ -29,8 +29,8 @@ class ThrottlerFactoryTest extends \PHPUnit_Framework_TestCase */ protected function setUp() { - $this->cacheAdapter = M::mock(CacheAdapterInterface::class); - $this->factory = new ThrottlerFactory($this->cacheAdapter); + $this->throttlerCache = M::mock(ThrottlerCacheInterface::class); + $this->factory = new ThrottlerFactory($this->throttlerCache); } public function testMakeElasticWindow() diff --git a/tests/Throttle/Factory/TimeAwareThrottlerFactoryTest.php b/tests/Throttle/Factory/TimeAwareThrottlerFactoryTest.php index 0561299..0813c3c 100644 --- a/tests/Throttle/Factory/TimeAwareThrottlerFactoryTest.php +++ b/tests/Throttle/Factory/TimeAwareThrottlerFactoryTest.php @@ -4,7 +4,7 @@ use Mockery as M; use Mockery\MockInterface; -use Sunspikes\Ratelimit\Cache\Adapter\CacheAdapterInterface; +use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Throttle\Factory\TimeAwareThrottlerFactory; use Sunspikes\Ratelimit\Throttle\Settings\FixedWindowSettings; use Sunspikes\Ratelimit\Throttle\Settings\LeakyBucketSettings; @@ -19,9 +19,9 @@ class TimeAwareThrottlerFactoryTest extends ThrottlerFactoryTest { /** - * @var CacheAdapterInterface|MockInterface + * @var ThrottlerCacheInterface|MockInterface */ - protected $cacheAdapter; + protected $throttlerCache; /** * @var TimeAdapterInterface|MockInterface @@ -34,9 +34,9 @@ class TimeAwareThrottlerFactoryTest extends ThrottlerFactoryTest protected function setUp() { $this->timeAdapter = M::mock(TimeAdapterInterface::class); - $this->cacheAdapter = M::mock(CacheAdapterInterface::class); + $this->throttlerCache = M::mock(ThrottlerCacheInterface::class); - $this->factory = new TimeAwareThrottlerFactory($this->cacheAdapter, $this->timeAdapter); + $this->factory = new TimeAwareThrottlerFactory($this->throttlerCache, $this->timeAdapter); } public function testMakeLeakyBucket() diff --git a/tests/Throttle/Throttler/AbstractWindowThrottlerTest.php b/tests/Throttle/Throttler/AbstractWindowThrottlerTest.php deleted file mode 100644 index b854706..0000000 --- a/tests/Throttle/Throttler/AbstractWindowThrottlerTest.php +++ /dev/null @@ -1,92 +0,0 @@ -timeAdapter = M::mock(TimeAdapterInterface::class); - $this->cacheAdapter = M::mock(CacheAdapterInterface::class); - - $this->throttler = $this->createThrottler('key'); - } - - public function testAccess() - { - $this->mockTimePassed(self::TIME_LIMIT + 2); - - $this->assertEquals(true, $this->throttler->access()); - } - - public function testCountWithMissingCacheItem() - { - $this->timeAdapter->shouldReceive('now')->once()->andReturn(self::INITIAL_TIME + 1); - $this->cacheAdapter->shouldReceive('get')->andThrow(ItemNotFoundException::class); - - self::assertEquals(0, $this->throttler->count()); - } - - public function testCountWithMoreTimePassedThanLimit() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1); - - $this->assertEquals(0, $this->throttler->count()); - } - - public function testCheck() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1); - - $this->assertTrue($this->throttler->check()); - } - - abstract public function testCountWithLessTimePassedThanLimit(); - - /** - * @param string $key - * - * @return AbstractWindowThrottler - */ - abstract protected function createThrottler($key); - - /** - * @param int $timeDiff - */ - protected function mockTimePassed($timeDiff) - { - $this->timeAdapter->shouldReceive('now')->andReturn(self::INITIAL_TIME + $timeDiff); - } -} diff --git a/tests/Throttle/Throttler/ElasticWindowThrottlerTest.php b/tests/Throttle/Throttler/ElasticWindowThrottlerTest.php deleted file mode 100644 index 78b5131..0000000 --- a/tests/Throttle/Throttler/ElasticWindowThrottlerTest.php +++ /dev/null @@ -1,84 +0,0 @@ -shouldReceive('set') - ->withAnyArgs() - ->andReturnNull(); - - $cacheAdapter->shouldReceive('get') - ->with('key') - ->andReturn(0, 1, 2, 3, 4); - - $this->throttler = new ElasticWindowThrottler($cacheAdapter, 'key', 3, self::TTL); - } - - public function testAccess() - { - $this->assertEquals(true, $this->throttler->access()); - } - - public function testHit() - { - $this->assertEquals(1, count($this->throttler->hit())); - } - - public function testClear() - { - $this->assertEquals(0, count($this->throttler->clear())); - } - - public function testCount() - { - $this->throttler->hit(); - $this->assertEquals(1, $this->throttler->count()); - } - - public function testCheck() - { - $this->assertTrue($this->throttler->check()); - } - - public function testThrottle() - { - $this->throttler->hit(); - $this->throttler->hit(); - $this->throttler->hit(); - $this->assertFalse($this->throttler->access()); - } - - public function testGetRetryTimeout() - { - $this->assertEquals(0, $this->throttler->getRetryTimeout()); - - $this->throttler->hit(); - $this->throttler->hit(); - $this->throttler->hit(); - - $this->assertEquals( - ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER * self::TTL, - $this->throttler->getRetryTimeout() - ); - } -} diff --git a/tests/Throttle/Throttler/FixedWindowThrottlerTest.php b/tests/Throttle/Throttler/FixedWindowThrottlerTest.php deleted file mode 100644 index c5c0e27..0000000 --- a/tests/Throttle/Throttler/FixedWindowThrottlerTest.php +++ /dev/null @@ -1,106 +0,0 @@ -cacheAdapter - ->shouldReceive('set') - ->with('key'.FixedWindowThrottler::CACHE_KEY_HITS, 1, self::CACHE_TTL) - ->once(); - - $this->cacheAdapter - ->shouldReceive('set') - ->with('key'.FixedWindowThrottler::CACHE_KEY_TIME, self::TIME_LIMIT + 2, self::CACHE_TTL) - ->once(); - - parent::testAccess(); - } - - public function testClear() - { - $this->timeAdapter->shouldReceive('now')->once()->andReturn(self::INITIAL_TIME + 3); - - $this->cacheAdapter - ->shouldReceive('set') - ->with('key'.FixedWindowThrottler::CACHE_KEY_TIME, self::INITIAL_TIME + 3, self::CACHE_TTL) - ->once(); - - $this->cacheAdapter - ->shouldReceive('set') - ->with('key'.FixedWindowThrottler::CACHE_KEY_HITS, 0, self::CACHE_TTL) - ->once(); - - $this->throttler->clear(); - } - - public function testCountWithLessTimePassedThanLimit() - { - //Less time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT / 6); - - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.FixedWindowThrottler::CACHE_KEY_HITS) - ->andReturn(self::HIT_LIMIT / 3); - - $this->assertEquals(self::HIT_LIMIT / 3, $this->throttler->count()); - } - - public function testGetRetryTimeoutPreLimit() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1); - - $this->assertEquals(0, $this->throttler->getRetryTimeout()); - } - - public function testGetRetryTimeoutPostLimit() - { - //Less time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT / 2); - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.FixedWindowThrottler::CACHE_KEY_HITS) - ->andReturn(self::HIT_LIMIT + 1); - - $this->assertEquals( - ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER / 2 * self::TIME_LIMIT, - $this->throttler->getRetryTimeout() - ); - } - - /** - * @inheritdoc - */ - protected function createThrottler($key) - { - return new FixedWindowThrottler( - $this->cacheAdapter, - $this->timeAdapter, - $key, - self::HIT_LIMIT, - self::TIME_LIMIT, - self::CACHE_TTL - ); - } - - /** - * @param int $timeDiff - */ - protected function mockTimePassed($timeDiff) - { - parent::mockTimePassed($timeDiff); - - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.FixedWindowThrottler::CACHE_KEY_TIME) - ->andReturn(self::INITIAL_TIME); - } -} diff --git a/tests/Throttle/Throttler/LeakyBucketThrottlerTest.php b/tests/Throttle/Throttler/LeakyBucketThrottlerTest.php deleted file mode 100644 index 53a9db2..0000000 --- a/tests/Throttle/Throttler/LeakyBucketThrottlerTest.php +++ /dev/null @@ -1,207 +0,0 @@ -timeAdapter = M::mock(TimeAdapterInterface::class); - $this->cacheAdapter = M::mock(CacheAdapterInterface::class); - - $this->throttler = new LeakyBucketThrottler( - $this->cacheAdapter, - $this->timeAdapter, - 'key', - self::TOKEN_LIMIT, - self::TIME_LIMIT, - self::THRESHOLD, - self::CACHE_TTL - ); - } - - public function testAccess() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1, 2); - $this->mockSetUsedCapacity( - 1, - (self::INITIAL_TIME + self::TIME_LIMIT + 1) / ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER - ); - - $this->assertEquals(true, $this->throttler->access()); - } - - public function testHitBelowThreshold() - { - // No time has passed - $this->mockTimePassed(0, 2); - - // Used tokens one below threshold - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TOKEN) - ->andReturn(self::THRESHOLD - 1); - - $this->mockSetUsedCapacity(self::THRESHOLD, self::INITIAL_TIME); - - $this->assertEquals(0, $this->throttler->hit()); - } - - public function testHitOnThreshold() - { - // No time has passed - $this->mockTimePassed(0, 2); - - // Used tokens on threshold - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TOKEN) - ->andReturn(self::THRESHOLD); - - $this->mockSetUsedCapacity(self::THRESHOLD + 1, self::INITIAL_TIME); - - $expectedWaitTime = self::TIME_LIMIT / (self::TOKEN_LIMIT - self::THRESHOLD); - $this->timeAdapter->shouldReceive('usleep') - ->with(ThrottlerInterface::MILLISECOND_TO_MICROSECOND_MULTIPLIER * $expectedWaitTime) - ->once() - ->ordered(); - - $this->assertEquals($expectedWaitTime, $this->throttler->hit()); - } - - public function testClear() - { - $this->timeAdapter->shouldReceive('now')->once()->andReturn(self::INITIAL_TIME + 1); - $this->mockSetUsedCapacity(0, self::INITIAL_TIME + 1); - - $this->throttler->clear(); - } - - public function testCountWithMissingCacheItem() - { - $this->timeAdapter->shouldReceive('now')->once()->andReturn(self::INITIAL_TIME + 1); - $this->cacheAdapter->shouldReceive('get')->andThrow(ItemNotFoundException::class); - - $this->mockSetUsedCapacity(0, self::INITIAL_TIME + 1); - - self::assertEquals(0, $this->throttler->count()); - } - - public function testCountWithMoreTimePassedThanLimit() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1, 1); - - $this->assertEquals(0, $this->throttler->count()); - } - - public function testCountWithLessTimePassedThanLimit() - { - // Time passed to refill 1/6 of tokens - $this->mockTimePassed(self::TIME_LIMIT / 6, 1); - - // Previously 1/2 of tokens used - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TOKEN) - ->andReturn(self::TOKEN_LIMIT / 2); - - // So bucket should be filled for 1/3 - $this->assertEquals(self::TOKEN_LIMIT / 3, $this->throttler->count()); - } - - public function testCheck() - { - //More time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT + 1, 1); - - $this->assertTrue($this->throttler->check()); - } - - - public function testGetRetryTimeoutPreLimit() - { - $this->mockTimePassed(self::TIME_LIMIT + 2, 1); - - $this->assertEquals(0, $this->throttler->getRetryTimeout()); - } - - public function testGetRetryTimeoutPostLimit() - { - $this->mockTimePassed(0, 1); - - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TOKEN) - ->andReturn(self::THRESHOLD); - - $this->assertSame((int) ceil(self::TIME_LIMIT / self::TOKEN_LIMIT), $this->throttler->getRetryTimeout()); - } - - /** - * @param int $tokens - * @param int $time - */ - private function mockSetUsedCapacity($tokens, $time) - { - $this->cacheAdapter - ->shouldReceive('set') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TOKEN, $tokens, self::CACHE_TTL) - ->once() - ->ordered('set-cache'); - - $this->cacheAdapter - ->shouldReceive('set') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TIME, $time, self::CACHE_TTL) - ->once() - ->ordered('set-cache'); - } - - /** - * @param int $timeDiff - * @param int $numCalls - */ - private function mockTimePassed($timeDiff, $numCalls) - { - $this->timeAdapter->shouldReceive('now') - ->times($numCalls) - ->andReturn((self::INITIAL_TIME + $timeDiff) / ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER); - - $this->cacheAdapter - ->shouldReceive('get') - ->with('key'.LeakyBucketThrottler::CACHE_KEY_TIME) - ->andReturn(self::INITIAL_TIME / ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER); - } -} diff --git a/tests/Throttle/Throttler/MovingWindowThrottlerTest.php b/tests/Throttle/Throttler/MovingWindowThrottlerTest.php deleted file mode 100644 index 204e057..0000000 --- a/tests/Throttle/Throttler/MovingWindowThrottlerTest.php +++ /dev/null @@ -1,101 +0,0 @@ -cacheAdapter->shouldReceive('get')->with('key')->andReturn(serialize([]))->byDefault(); - } - - public function testAccess() - { - $this->cacheAdapter->shouldReceive('get') - ->with('key') - ->andReturn(serialize([self::INITIAL_TIME - self::TIME_LIMIT - 1 => self::HIT_LIMIT + 1])); - - $this->cacheAdapter->shouldReceive('set') - ->with('key', serialize([self::INITIAL_TIME + self::TIME_LIMIT + 2 => 1]), self::CACHE_TTL) - ->once(); - - parent::testAccess(); - } - - public function testClear() - { - $this->cacheAdapter->shouldReceive('set') - ->with('key', serialize([]), self::CACHE_TTL) - ->once(); - - $this->throttler->clear(); - } - - public function testCountWithLessTimePassedThanLimit() - { - //Less time has passed than the given window - $this->mockTimePassed(self::TIME_LIMIT / 6); - - $this->cacheAdapter->shouldReceive('get') - ->with('key') - ->andReturn(serialize([ - self::INITIAL_TIME - self::TIME_LIMIT => self::HIT_LIMIT / 2, - self::INITIAL_TIME => self::HIT_LIMIT / 3, - ])); - - $this->assertEquals(self::HIT_LIMIT / 3, $this->throttler->count()); - } - - public function testGetRetryTimeoutPreLimit() - { - $this->mockTimePassed(self::TIME_LIMIT + 1); - - $this->cacheAdapter->shouldReceive('get') - ->with('key') - ->andReturn(serialize([self::INITIAL_TIME - self::TIME_LIMIT - 1 => self::HIT_LIMIT + 1])); - - $this->assertEquals(0, $this->throttler->getRetryTimeout()); - } - - public function testGetRetryTimeoutPostLimit() - { - $this->mockTimePassed(1); - - $this->cacheAdapter->shouldReceive('get') - ->with('key') - ->andReturn(serialize([ - self::INITIAL_TIME => 1, - self::INITIAL_TIME + 1 => 1, // <-- This is the timestamp which should expire before can be retried - self::INITIAL_TIME + self::TIME_LIMIT - 1 => self::HIT_LIMIT - 2 - ])); - - $this->assertEquals( - ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER * (self::TIME_LIMIT - 1), - $this->throttler->getRetryTimeout() - ); - } - - /** - * @inheritdoc - */ - protected function createThrottler($key) - { - return new MovingWindowThrottler( - $this->cacheAdapter, - $this->timeAdapter, - $key, - self::HIT_LIMIT, - self::TIME_LIMIT, - self::CACHE_TTL - ); - } -} diff --git a/tests/Throttle/Throttler/RetrialQueueThrottlerTest.php b/tests/Throttle/Throttler/RetrialQueueThrottlerTest.php deleted file mode 100644 index 520672a..0000000 --- a/tests/Throttle/Throttler/RetrialQueueThrottlerTest.php +++ /dev/null @@ -1,104 +0,0 @@ -timeAdapter = M::mock(TimeAdapterInterface::class); - $this->cacheAdapter = M::mock(CacheAdapterInterface::class); - - $this->internalThrottler = M::mock(RetriableThrottlerInterface::class); - $this->internalThrottler->shouldReceive('getLimit')->andReturn(self::HIT_LIMIT); - $this->internalThrottler->shouldReceive('getTime')->andReturn(self::TIME_LIMIT); - - $this->throttler = new RetrialQueueThrottler($this->internalThrottler, $this->timeAdapter); - } - - public function testAccess() - { - $this->internalThrottler->shouldReceive('check')->andReturn(true); - $this->internalThrottler->shouldReceive('getRetryTimeout')->andReturn(0); - $this->internalThrottler->shouldReceive('hit')->once(); - - $this->timeAdapter->shouldNotReceive('usleep'); - - $this->assertTrue($this->throttler->access()); - } - - public function testHitBelowThreshold() - { - $this->internalThrottler->shouldReceive('getRetryTimeout')->andReturn(0); - $this->internalThrottler->shouldReceive('hit')->once()->andReturnSelf(); - - $this->timeAdapter->shouldNotReceive('usleep'); - - $this->assertEquals($this->internalThrottler, $this->throttler->hit()); - } - - public function testHitOnThreshold() - { - $this->internalThrottler->shouldReceive('getRetryTimeout') - ->andReturn(ThrottlerInterface::SECOND_TO_MILLISECOND_MULTIPLIER); - $this->internalThrottler->shouldReceive('hit')->once()->andReturnSelf(); - - $this->timeAdapter->shouldReceive('usleep')->with(1e6)->once(); - - $this->assertEquals($this->internalThrottler, $this->throttler->hit()); - } - - public function testClear() - { - $this->internalThrottler->shouldReceive('clear')->once(); - - $this->throttler->clear(); - } - - public function testCount() - { - $this->internalThrottler->shouldReceive('count')->andReturn(1); - - self::assertEquals(1, $this->throttler->count()); - } - - public function testCheck() - { - $this->internalThrottler->shouldReceive('check')->andReturn(true); - - self::assertTrue($this->throttler->check()); - } -} From 8bf27138aa4b32d7864c826f03baf407dc1345a7 Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Tue, 7 Nov 2017 17:47:17 +0100 Subject: [PATCH 2/6] Updated serialization logic --- build/logs/clover.xml | 3294 ----------------- src/Cache/AbstractCacheItem.php | 38 + src/Cache/ThrottlerCache.php | 94 +- src/Cache/ThrottlerCacheInterface.php | 8 + src/Cache/ThrottlerItemInterface.php | 9 +- src/Throttle/Entity/CacheCount.php | 41 +- src/Throttle/Entity/CacheHitMapping.php | 41 +- src/Throttle/Entity/CacheTime.php | 41 +- .../Throttler/ElasticWindowThrottler.php | 8 +- .../Throttler/FixedWindowThrottler.php | 6 +- .../Throttler/LeakyBucketThrottler.php | 7 +- .../Throttler/MovingWindowThrottler.php | 8 +- tests/Cache/ThrottlerCacheTest.php | 7 +- .../Functional/AbstractThrottlerTestCase.php | 1 - 14 files changed, 149 insertions(+), 3454 deletions(-) delete mode 100644 build/logs/clover.xml create mode 100644 src/Cache/AbstractCacheItem.php diff --git a/build/logs/clover.xml b/build/logs/clover.xml deleted file mode 100644 index fe8b4a0..0000000 --- a/build/logs/clover.xml +++ /dev/null @@ -1,3294 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Cache/AbstractCacheItem.php b/src/Cache/AbstractCacheItem.php new file mode 100644 index 0000000..aa42db0 --- /dev/null +++ b/src/Cache/AbstractCacheItem.php @@ -0,0 +1,38 @@ +fromArray($array); + } + + /** + * @return array + */ + public function serialize() + { + return json_encode($this->toArray()); + } + + /** + * Wake up call to build the cache item object from array representation + * + * @param array $array + */ + abstract protected function fromArray(array $array); + + /** + * Get the array representation of the object + * + * @return array + */ + abstract protected function toArray(): array; +} \ No newline at end of file diff --git a/src/Cache/ThrottlerCache.php b/src/Cache/ThrottlerCache.php index a6cd2ba..1170de2 100644 --- a/src/Cache/ThrottlerCache.php +++ b/src/Cache/ThrottlerCache.php @@ -48,77 +48,43 @@ public function __construct(CacheItemPoolInterface $cacheItemPool) } /** - * @param string $key - * - * @return ThrottlerItemInterface + * @inheritdoc */ public function getItem(string $key): ThrottlerItemInterface { - $item = $this->getThrottlerItem($key); - - return $item; - } + try { + $item = $this->cacheItemPool->getItem($key); - /** - * @param string $key - * - * @return bool - */ - public function hasItem(string $key): bool - { - return $this->cacheItemPool->hasItem($key); - } + if ($item->isHit()) { + $throttlerItem = unserialize($item->get(), ['allowed_classes' => true]); - /** - * @param string $key - * @param mixed $item - * @return bool - */ - public function setItem(string $key, ThrottlerItemInterface $item): bool - { - return $this->setThrottlerItem($key, $item); - } + if ($throttlerItem instanceof ThrottlerItemInterface) { + return $throttlerItem; + } + } + } catch (PsrCacheException $e) { + throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); + } - /** - * @param string $key - */ - public function removeItem(string $key) - { - $this->cacheItemPool->deleteItem($key); + throw new ItemNotFoundException('Item not found.'); } /** - * @param string $key - * - * @return ThrottlerItemInterface - * @throws CacheAdapterException - * @throws ItemNotFoundException + * @inheritdoc */ - private function getThrottlerItem(string $key): ThrottlerItemInterface + public function hasItem(string $key): bool { try { - $item = $this->cacheItemPool->getItem($key); - - if ($item->isHit()) { - $params = json_decode($item->get(), true); - $class = $params['class']; - - return $class::createFromArray($params['data']); - } + return $this->cacheItemPool->hasItem($key); } catch (PsrCacheException $e) { + throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); } - - throw new ItemNotFoundException('Item not found.'); } /** - * @param string $key - * @param ThrottlerItemInterface $item - * - * @return bool - * @throws CacheAdapterException + * @inheritdoc */ - private function setThrottlerItem(string $key, ThrottlerItemInterface $item) + public function setItem(string $key, ThrottlerItemInterface $item): bool { try { $cacheItem = $this->cacheItemPool->getItem($key); @@ -130,17 +96,21 @@ private function setThrottlerItem(string $key, ThrottlerItemInterface $item) throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); } - $cacheItem->set( - json_encode( - [ - 'class' => get_class($item), - 'data' => $item, - ] - ) - ); - + $cacheItem->set(serialize($item)); $cacheItem->expiresAfter($item->getTtl()); return $this->cacheItemPool->save($cacheItem); } + + /** + * @inheritdoc + */ + public function removeItem(string $key) + { + try { + $this->cacheItemPool->deleteItem($key); + } catch (PsrCacheException $e) { + throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); + } + } } \ No newline at end of file diff --git a/src/Cache/ThrottlerCacheInterface.php b/src/Cache/ThrottlerCacheInterface.php index e6a3308..a499b3c 100644 --- a/src/Cache/ThrottlerCacheInterface.php +++ b/src/Cache/ThrottlerCacheInterface.php @@ -25,12 +25,17 @@ namespace Sunspikes\Ratelimit\Cache; +use Sunspikes\Ratelimit\Cache\Exception\CacheAdapterException; +use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException; + interface ThrottlerCacheInterface { /** * @param string $key * * @return ThrottlerItemInterface + * @throws CacheAdapterException + * @throws ItemNotFoundException */ public function getItem(string $key): ThrottlerItemInterface; @@ -38,6 +43,7 @@ public function getItem(string $key): ThrottlerItemInterface; * @param string $key * @param ThrottlerItemInterface $item * @return bool + * @throws CacheAdapterException */ public function setItem(string $key, ThrottlerItemInterface $item): bool; @@ -45,11 +51,13 @@ public function setItem(string $key, ThrottlerItemInterface $item): bool; * @param string $key * * @return bool + * @throws CacheAdapterException */ public function hasItem(string $key): bool; /** * @param string $key + * @throws CacheAdapterException */ public function removeItem(string $key); } \ No newline at end of file diff --git a/src/Cache/ThrottlerItemInterface.php b/src/Cache/ThrottlerItemInterface.php index aa46f36..e1d6e30 100644 --- a/src/Cache/ThrottlerItemInterface.php +++ b/src/Cache/ThrottlerItemInterface.php @@ -2,15 +2,8 @@ namespace Sunspikes\Ratelimit\Cache; -interface ThrottlerItemInterface extends \JsonSerializable +interface ThrottlerItemInterface extends \Serializable { - /** - * @param array $array - * - * @return ThrottlerItemInterface - */ - public static function createFromArray(array $array): ThrottlerItemInterface; - /** * @return int|null */ diff --git a/src/Throttle/Entity/CacheCount.php b/src/Throttle/Entity/CacheCount.php index 3dcd914..1be47e5 100644 --- a/src/Throttle/Entity/CacheCount.php +++ b/src/Throttle/Entity/CacheCount.php @@ -2,14 +2,15 @@ namespace Sunspikes\Ratelimit\Throttle\Entity; +use Sunspikes\Ratelimit\Cache\AbstractCacheItem; use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; -class CacheCount implements ThrottlerItemInterface +class CacheCount extends AbstractCacheItem implements ThrottlerItemInterface { /** @var int $count */ private $count; - /** @var int $ttl */ + /** @var int|null $ttl */ private $ttl; /** @@ -23,42 +24,38 @@ public function __construct(int $count, int $ttl = null) } /** - * @param array $array - * - * @return ThrottlerItemInterface + * @return int */ - public static function createFromArray(array $array): ThrottlerItemInterface + public function getCount(): int { - return new static( - $array['count'], - $array['ttl'] - ); + return $this->count; } /** - * @return array + * @inheritdoc */ - public function jsonSerialize() + public function getTtl() { - return [ - 'count' => $this->count, - 'ttl' => $this->ttl, - ]; + return $this->ttl; } /** - * @return int + * @inheritdoc */ - public function getCount(): int + protected function fromArray(array $array) { - return $this->count; + $this->count = $array['count']; + $this->ttl = $array['ttl']; } /** - * @return int|null + * @inheritdoc */ - public function getTtl() + protected function toArray(): array { - return $this->ttl; + return [ + 'count' => $this->count, + 'ttl' => $this->ttl, + ]; } } \ No newline at end of file diff --git a/src/Throttle/Entity/CacheHitMapping.php b/src/Throttle/Entity/CacheHitMapping.php index cba47a2..0b2e6c6 100644 --- a/src/Throttle/Entity/CacheHitMapping.php +++ b/src/Throttle/Entity/CacheHitMapping.php @@ -3,13 +3,14 @@ namespace Sunspikes\Ratelimit\Throttle\Entity; use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; +use Sunspikes\Ratelimit\Cache\AbstractCacheItem; -class CacheHitMapping implements ThrottlerItemInterface +class CacheHitMapping extends AbstractCacheItem implements ThrottlerItemInterface { /** @var array $hitMapping */ private $hitMapping; - /** @var int $ttl */ + /** @var int|null $ttl */ private $ttl; /** @@ -23,42 +24,38 @@ public function __construct(array $hitMapping, int $ttl = null) } /** - * @param array $array - * - * @return ThrottlerItemInterface + * @return array */ - public static function createFromArray(array $array): ThrottlerItemInterface + public function getHitMapping(): array { - return new static( - $array['hitMapping'], - $array['ttl'] - ); + return $this->hitMapping; } /** - * @return array + * @inheritdoc */ - public function jsonSerialize() + public function getTtl() { - return [ - 'hitMapping' => $this->hitMapping, - 'ttl' => $this->ttl, - ]; + return $this->ttl; } /** - * @return array + * @inheritdoc */ - public function getHitMapping(): array + protected function fromArray(array $array) { - return $this->hitMapping; + $this->hitMapping = $array['hitMapping']; + $this->ttl = $array['ttl']; } /** - * @return int|null + * @inheritdoc */ - public function getTtl() + protected function toArray(): array { - return $this->ttl; + return [ + 'hitMapping' => $this->hitMapping, + 'ttl' => $this->ttl, + ]; } } \ No newline at end of file diff --git a/src/Throttle/Entity/CacheTime.php b/src/Throttle/Entity/CacheTime.php index 50f0a56..87c748e 100644 --- a/src/Throttle/Entity/CacheTime.php +++ b/src/Throttle/Entity/CacheTime.php @@ -2,14 +2,15 @@ namespace Sunspikes\Ratelimit\Throttle\Entity; +use Sunspikes\Ratelimit\Cache\AbstractCacheItem; use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; -class CacheTime implements ThrottlerItemInterface +class CacheTime extends AbstractCacheItem implements ThrottlerItemInterface { /** @var float $limit */ private $time; - /** @var int $ttl */ + /** @var int|null $ttl */ private $ttl; /** @@ -23,42 +24,38 @@ public function __construct(float $time, int $ttl = null) } /** - * @param array $array - * - * @return ThrottlerItemInterface + * @return float */ - public static function createFromArray(array $array): ThrottlerItemInterface + public function getTime(): float { - return new static( - $array['time'], - $array['ttl'] - ); + return $this->time; } /** - * @return array + * @inheritdoc */ - public function jsonSerialize() + public function getTtl() { - return [ - 'time' => $this->time, - 'ttl' => $this->ttl, - ]; + return $this->ttl; } /** - * @return float + * @inheritdoc */ - public function getTime(): float + protected function fromArray(array $array) { - return $this->time; + $this->time = $array['time']; + $this->ttl = $array['ttl']; } /** - * @return float|null + * @inheritdoc */ - public function getTtl() + protected function toArray(): array { - return $this->ttl; + return [ + 'time' => $this->time, + 'ttl' => $this->ttl, + ]; } } \ No newline at end of file diff --git a/src/Throttle/Throttler/ElasticWindowThrottler.php b/src/Throttle/Throttler/ElasticWindowThrottler.php index 38a5949..58e0b78 100644 --- a/src/Throttle/Throttler/ElasticWindowThrottler.php +++ b/src/Throttle/Throttler/ElasticWindowThrottler.php @@ -74,9 +74,7 @@ public function access() public function hit() { $this->counter = $this->count() + 1; - $item = new CacheCount($this->counter, $this->ttl); - - $this->cache->setItem($this->key, $item); + $this->cache->setItem($this->key, new CacheCount($this->counter, $this->ttl)); return $this; } @@ -87,9 +85,7 @@ public function hit() public function clear() { $this->counter = 0; - $item = new CacheCount($this->counter, $this->ttl); - - $this->cache->setItem($this->key, $item); + $this->cache->setItem($this->key, new CacheCount($this->counter, $this->ttl)); return $this; } diff --git a/src/Throttle/Throttler/FixedWindowThrottler.php b/src/Throttle/Throttler/FixedWindowThrottler.php index 6e31913..6ffba70 100644 --- a/src/Throttle/Throttler/FixedWindowThrottler.php +++ b/src/Throttle/Throttler/FixedWindowThrottler.php @@ -84,8 +84,7 @@ public function count() public function clear() { $this->setCachedHitCount(0); - $item = new CacheTime($this->timeProvider->now(), $this->cacheTtl); - $this->cache->setItem($this->getTimeCacheKey(), $item); + $this->cache->setItem($this->getTimeCacheKey(), new CacheTime($this->timeProvider->now(), $this->cacheTtl)); } /** @@ -127,8 +126,7 @@ private function getCachedHitCount() private function setCachedHitCount($hitCount) { $this->hitCount = $hitCount; - $item = new CacheCount($hitCount, $this->cacheTtl); - $this->cache->setItem($this->getHitsCacheKey(), $item); + $this->cache->setItem($this->getHitsCacheKey(), new CacheCount($hitCount, $this->cacheTtl)); } /** diff --git a/src/Throttle/Throttler/LeakyBucketThrottler.php b/src/Throttle/Throttler/LeakyBucketThrottler.php index b827706..a09139d 100644 --- a/src/Throttle/Throttler/LeakyBucketThrottler.php +++ b/src/Throttle/Throttler/LeakyBucketThrottler.php @@ -211,11 +211,8 @@ private function getWaitTime($tokenCount) */ private function setUsedCapacity($tokens) { - $countItem = new CacheCount($tokens, $this->cacheTtl); - $this->cache->setItem($this->getTokenCacheKey(), $countItem); - - $timeItem = new CacheTime($this->timeProvider->now(), $this->cacheTtl); - $this->cache->setItem($this->getTimeCacheKey(), $timeItem); + $this->cache->setItem($this->getTokenCacheKey(), new CacheCount($tokens, $this->cacheTtl)); + $this->cache->setItem($this->getTimeCacheKey(), new CacheTime($this->timeProvider->now(), $this->cacheTtl)); } /** diff --git a/src/Throttle/Throttler/MovingWindowThrottler.php b/src/Throttle/Throttler/MovingWindowThrottler.php index ce57236..84efa03 100644 --- a/src/Throttle/Throttler/MovingWindowThrottler.php +++ b/src/Throttle/Throttler/MovingWindowThrottler.php @@ -51,9 +51,7 @@ public function hit() //Adds 1 recorded hit to the mapping entry for the current timestamp $this->hitCountMapping[$timestamp]++; - - $item = new CacheHitMapping($this->hitCountMapping, $this->cacheTtl); - $this->cache->setItem($this->key, $item); + $this->cache->setItem($this->key, new CacheHitMapping($this->hitCountMapping, $this->cacheTtl)); return $this; } @@ -97,9 +95,7 @@ public function getRetryTimeout() public function clear() { $this->hitCountMapping = []; - - $item = new CacheHitMapping($this->hitCountMapping, $this->cacheTtl); - $this->cache->setItem($this->key, $item); + $this->cache->setItem($this->key, new CacheHitMapping($this->hitCountMapping, $this->cacheTtl)); } private function updateHitCount() diff --git a/tests/Cache/ThrottlerCacheTest.php b/tests/Cache/ThrottlerCacheTest.php index b438b91..35c45dc 100644 --- a/tests/Cache/ThrottlerCacheTest.php +++ b/tests/Cache/ThrottlerCacheTest.php @@ -17,7 +17,7 @@ public function testGetItemFound() $countItem = new CacheCount(1); $cacheItem = new CacheItem($key); - $cacheItem->set(json_encode(['class' => CacheCount::class, 'data' => $countItem])); + $cacheItem->set(serialize($countItem)); $cacheItemPool = M::mock(CacheItemPoolInterface::class); $cacheItemPool->shouldReceive('getItem') @@ -36,10 +36,13 @@ public function testGetItemFound() public function testGetItemNotFound() { $key = 'key'; + + $cacheItem = new CacheItem('none'); + $cacheItemPool = M::mock(CacheItemPoolInterface::class); $cacheItemPool->shouldReceive('getItem') ->with($key) - ->andThrow(CachePoolException::class); + ->andReturn($cacheItem); $cache = new ThrottlerCache($cacheItemPool); $cache->getItem($key); diff --git a/tests/Functional/AbstractThrottlerTestCase.php b/tests/Functional/AbstractThrottlerTestCase.php index 5c87f38..3628aed 100644 --- a/tests/Functional/AbstractThrottlerTestCase.php +++ b/tests/Functional/AbstractThrottlerTestCase.php @@ -44,7 +44,6 @@ public function testThrottlePostLimit() $throttle->hit(); } - $this->assertFalse($throttle->check()); } From 0a7ad196acc778e4d0b636cf4685932581749265 Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Wed, 8 Nov 2017 10:36:49 +0100 Subject: [PATCH 3/6] Added missing license texts --- README.md | 4 ++-- src/Cache/AbstractCacheItem.php | 23 +++++++++++++++++++++++ src/Cache/ThrottlerItemInterface.php | 24 ++++++++++++++++++++++++ src/Throttle/Entity/CacheCount.php | 24 ++++++++++++++++++++++++ src/Throttle/Entity/CacheHitMapping.php | 24 ++++++++++++++++++++++++ src/Throttle/Entity/CacheTime.php | 23 +++++++++++++++++++++++ 6 files changed, 120 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b15ceb6..b8bbb70 100644 --- a/README.md +++ b/README.md @@ -188,11 +188,11 @@ the request is delayed until the internal throttler has capacity again. $settings = new RetrialQueueSettings(new LeakyBucketSettings(120, 60000, 120)); ``` -## Author +## Authors Krishnaprasad MG [@sunspikes] -@Feijs +Mike Feijs [@Feijs] ## Contributing diff --git a/src/Cache/AbstractCacheItem.php b/src/Cache/AbstractCacheItem.php index aa42db0..9f90b73 100644 --- a/src/Cache/AbstractCacheItem.php +++ b/src/Cache/AbstractCacheItem.php @@ -1,4 +1,27 @@ + * + * 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. + */ namespace Sunspikes\Ratelimit\Cache; diff --git a/src/Cache/ThrottlerItemInterface.php b/src/Cache/ThrottlerItemInterface.php index e1d6e30..fcd647f 100644 --- a/src/Cache/ThrottlerItemInterface.php +++ b/src/Cache/ThrottlerItemInterface.php @@ -1,4 +1,28 @@ + * + * 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. + */ + namespace Sunspikes\Ratelimit\Cache; diff --git a/src/Throttle/Entity/CacheCount.php b/src/Throttle/Entity/CacheCount.php index 1be47e5..923dfc8 100644 --- a/src/Throttle/Entity/CacheCount.php +++ b/src/Throttle/Entity/CacheCount.php @@ -1,4 +1,28 @@ + * + * 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. + */ + namespace Sunspikes\Ratelimit\Throttle\Entity; diff --git a/src/Throttle/Entity/CacheHitMapping.php b/src/Throttle/Entity/CacheHitMapping.php index 0b2e6c6..83e1d29 100644 --- a/src/Throttle/Entity/CacheHitMapping.php +++ b/src/Throttle/Entity/CacheHitMapping.php @@ -1,4 +1,28 @@ + * + * 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. + */ + namespace Sunspikes\Ratelimit\Throttle\Entity; diff --git a/src/Throttle/Entity/CacheTime.php b/src/Throttle/Entity/CacheTime.php index 87c748e..73fa51f 100644 --- a/src/Throttle/Entity/CacheTime.php +++ b/src/Throttle/Entity/CacheTime.php @@ -1,4 +1,27 @@ + * + * 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. + */ namespace Sunspikes\Ratelimit\Throttle\Entity; From 6901f966ce8220b7c3138b12ac9693f654b2edc0 Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Wed, 8 Nov 2017 11:31:01 +0100 Subject: [PATCH 4/6] Upgrade to phpunit 6 --- README.md | 2 +- composer.json | 4 ++-- tests/Cache/ThrottlerCacheTest.php | 4 ++-- tests/Functional/AbstractThrottlerTestCase.php | 10 ++++++++-- tests/Functional/FixedWindowTest.php | 2 -- tests/RatelimiterTest.php | 7 +++++-- tests/Throttle/Entity/DataTest.php | 3 ++- tests/Throttle/Factory/ThrottlerFactoryTest.php | 11 ++++++++--- tests/Throttle/Hydrator/ArrayHydratorTest.php | 3 ++- tests/Throttle/Hydrator/HydratorFactoryTest.php | 3 ++- tests/Throttle/Hydrator/StringHydratorTest.php | 3 ++- .../Throttle/Settings/AbstractWindowSettingsTest.php | 7 +++++-- tests/Throttle/Settings/ElasticWindowSettingsTest.php | 7 +++++-- tests/Throttle/Settings/FixedWindowSettingsTest.php | 1 - tests/Throttle/Settings/LeakyBucketSettingsTest.php | 7 +++++-- tests/Throttle/Settings/MovingWindowSettingsTest.php | 1 - 16 files changed, 49 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b8bbb70..894ee55 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ by including `sunspikes/php-ratelimiter` in your project composer.json require: ### Without Composer -You can also download it from [Github] (https://github.com/sunspikes/php-ratelimiter), +You can also download it from [Github](https://github.com/sunspikes/php-ratelimiter), but no autoloader is provided so you'll need to register it with your own PSR-4 compatible autoloader. diff --git a/composer.json b/composer.json index 91dedf1..406164c 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,12 @@ }, "require-dev": { "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.7.6", + "phpunit/phpunit": "^6.4", "cache/cache": "^0.4", "scrutinizer/ocular": "^1.1" }, "suggest": { - "cache/cache": "Rate limiter needs one of PSR-6 compatible cache adapters" + "cache/cache": "Rate limiter needs one of the PSR-6 compatible cache adapters" }, "autoload": { "psr-4": { diff --git a/tests/Cache/ThrottlerCacheTest.php b/tests/Cache/ThrottlerCacheTest.php index 35c45dc..31a5cbe 100644 --- a/tests/Cache/ThrottlerCacheTest.php +++ b/tests/Cache/ThrottlerCacheTest.php @@ -3,13 +3,13 @@ namespace Sunspikes\Tests\Ratelimit\Cache; use Cache\Adapter\Common\CacheItem; -use Cache\Adapter\Common\Exception\CachePoolException; use Mockery as M; +use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; use Sunspikes\Ratelimit\Cache\ThrottlerCache; use Sunspikes\Ratelimit\Throttle\Entity\CacheCount; -class ThrottlerCacheTest extends \PHPUnit_Framework_TestCase +class ThrottlerCacheTest extends TestCase { public function testGetItemFound() { diff --git a/tests/Functional/AbstractThrottlerTestCase.php b/tests/Functional/AbstractThrottlerTestCase.php index 3628aed..47850de 100644 --- a/tests/Functional/AbstractThrottlerTestCase.php +++ b/tests/Functional/AbstractThrottlerTestCase.php @@ -3,23 +3,29 @@ namespace Sunspikes\Tests\Ratelimit\Functional; use Cache\Adapter\PHPArray\ArrayCachePool; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Cache\ThrottlerCache; use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\RateLimiter; -abstract class AbstractThrottlerTestCase extends \PHPUnit_Framework_TestCase +abstract class AbstractThrottlerTestCase extends TestCase { /** * @var Ratelimiter */ protected $ratelimiter; + /** + * @var array + */ + protected $cache = []; + /** * @inheritdoc */ protected function setUp() { - $pool = new ArrayCachePool(); + $pool = new ArrayCachePool(null, $this->cache); $cache = new ThrottlerCache($pool); $this->ratelimiter = $this->createRatelimiter($cache); diff --git a/tests/Functional/FixedWindowTest.php b/tests/Functional/FixedWindowTest.php index 0e6c41f..8378526 100644 --- a/tests/Functional/FixedWindowTest.php +++ b/tests/Functional/FixedWindowTest.php @@ -3,8 +3,6 @@ namespace Sunspikes\Tests\Ratelimit\Functional; use Mockery as M; -use Sunspikes\Ratelimit\Cache\Adapter\DesarrollaCacheAdapter; -use Sunspikes\Ratelimit\Cache\Factory\FactoryInterface; use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\RateLimiter; use Sunspikes\Ratelimit\Throttle\Factory\TimeAwareThrottlerFactory; diff --git a/tests/RatelimiterTest.php b/tests/RatelimiterTest.php index 4d28d9c..172bb8b 100644 --- a/tests/RatelimiterTest.php +++ b/tests/RatelimiterTest.php @@ -3,6 +3,7 @@ namespace Sunspikes\Tests\Ratelimit; use Mockery as M; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\RateLimiter; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Factory\FactoryInterface as ThrottlerFactoryInterface; @@ -12,7 +13,7 @@ use Sunspikes\Ratelimit\Throttle\Settings\ThrottleSettingsInterface; use Sunspikes\Ratelimit\Throttle\Throttler\ThrottlerInterface; -class RatelimiterTest extends \PHPUnit_Framework_TestCase +class RatelimiterTest extends TestCase { /** * @var ThrottleSettingsInterface|M\MockInterface @@ -50,9 +51,11 @@ protected function setUp() ); } + /** + * @expectedException \InvalidArgumentException + */ public function testGetWithInvalidData() { - $this->setExpectedException(\InvalidArgumentException::class); $this->ratelimiter->get(''); } diff --git a/tests/Throttle/Entity/DataTest.php b/tests/Throttle/Entity/DataTest.php index 591e0f9..edde32c 100644 --- a/tests/Throttle/Entity/DataTest.php +++ b/tests/Throttle/Entity/DataTest.php @@ -2,9 +2,10 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Entity; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Entity\Data; -class DataTest extends \PHPUnit_Framework_TestCase +class DataTest extends TestCase { /** * @var Data diff --git a/tests/Throttle/Factory/ThrottlerFactoryTest.php b/tests/Throttle/Factory/ThrottlerFactoryTest.php index 75290ca..f01a847 100644 --- a/tests/Throttle/Factory/ThrottlerFactoryTest.php +++ b/tests/Throttle/Factory/ThrottlerFactoryTest.php @@ -4,6 +4,7 @@ use Mockery as M; use Mockery\MockInterface; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Factory\ThrottlerFactory; @@ -12,7 +13,7 @@ use Sunspikes\Ratelimit\Throttle\Throttler\ElasticWindowThrottler; use Sunspikes\Ratelimit\Throttle\Factory\FactoryInterface; -class ThrottlerFactoryTest extends \PHPUnit_Framework_TestCase +class ThrottlerFactoryTest extends TestCase { /** * @var ThrottlerCacheInterface|MockInterface @@ -41,18 +42,22 @@ public function testMakeElasticWindow() ); } + /** + * @expectedException InvalidArgumentException + */ public function testInvalidSettings() { - $this->setExpectedException(\InvalidArgumentException::class); $this->factory->make($this->getData(), new ElasticWindowSettings()); } + /** + * @expectedException \InvalidArgumentException + */ public function testUnknownSettings() { $settings = M::mock(ThrottleSettingsInterface::class); $settings->shouldReceive('isValid')->andReturn(true); - $this->setExpectedException(\InvalidArgumentException::class); $this->factory->make($this->getData(), $settings); } diff --git a/tests/Throttle/Hydrator/ArrayHydratorTest.php b/tests/Throttle/Hydrator/ArrayHydratorTest.php index e852d7b..7a6434b 100644 --- a/tests/Throttle/Hydrator/ArrayHydratorTest.php +++ b/tests/Throttle/Hydrator/ArrayHydratorTest.php @@ -2,10 +2,11 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Hydrator; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Hydrator\ArrayHydrator; -class ArrayHydratorTest extends \PHPUnit_Framework_TestCase +class ArrayHydratorTest extends TestCase { public function testHydrate() { diff --git a/tests/Throttle/Hydrator/HydratorFactoryTest.php b/tests/Throttle/Hydrator/HydratorFactoryTest.php index 2661c16..960d704 100644 --- a/tests/Throttle/Hydrator/HydratorFactoryTest.php +++ b/tests/Throttle/Hydrator/HydratorFactoryTest.php @@ -2,9 +2,10 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Hydrator; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Hydrator\HydratorFactory; -class HydratorFactoryTest extends \PHPUnit_Framework_TestCase +class HydratorFactoryTest extends TestCase { private $hydratorFactory; diff --git a/tests/Throttle/Hydrator/StringHydratorTest.php b/tests/Throttle/Hydrator/StringHydratorTest.php index 9afb842..eedaa77 100644 --- a/tests/Throttle/Hydrator/StringHydratorTest.php +++ b/tests/Throttle/Hydrator/StringHydratorTest.php @@ -2,10 +2,11 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Hydrator; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Entity\Data; use Sunspikes\Ratelimit\Throttle\Hydrator\StringHydrator; -class StringHydratorTest extends \PHPUnit_Framework_TestCase +class StringHydratorTest extends TestCase { public function testHydrate() { diff --git a/tests/Throttle/Settings/AbstractWindowSettingsTest.php b/tests/Throttle/Settings/AbstractWindowSettingsTest.php index 1e22e50..e6571ad 100644 --- a/tests/Throttle/Settings/AbstractWindowSettingsTest.php +++ b/tests/Throttle/Settings/AbstractWindowSettingsTest.php @@ -3,10 +3,11 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Settings; use Mockery as M; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Settings\AbstractWindowSettings; use Sunspikes\Ratelimit\Throttle\Settings\ThrottleSettingsInterface; -abstract class AbstractWindowSettingsTest extends \PHPUnit_Framework_TestCase +abstract class AbstractWindowSettingsTest extends TestCase { public function testMergeWithEmpty() { @@ -26,9 +27,11 @@ public function testMergeWithNonEmpty() self::assertEquals(null, $mergedSettings->getCacheTtl()); } + /** + * @expectedException \InvalidArgumentException + */ public function testInvalidMerge() { - $this->setExpectedException(\InvalidArgumentException::class); $this->getSettings()->merge(M::mock(ThrottleSettingsInterface::class)); } diff --git a/tests/Throttle/Settings/ElasticWindowSettingsTest.php b/tests/Throttle/Settings/ElasticWindowSettingsTest.php index 87c7d0d..5de3aad 100644 --- a/tests/Throttle/Settings/ElasticWindowSettingsTest.php +++ b/tests/Throttle/Settings/ElasticWindowSettingsTest.php @@ -3,10 +3,11 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Settings; use Mockery as M; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Settings\ElasticWindowSettings; use Sunspikes\Ratelimit\Throttle\Settings\ThrottleSettingsInterface; -class ElasticWindowSettingsTest extends \PHPUnit_Framework_TestCase +class ElasticWindowSettingsTest extends TestCase { public function testMergeWithEmpty() { @@ -26,9 +27,11 @@ public function testMergeWithNonEmpty() self::assertEquals(700, $mergedSettings->getTime()); } + /** + * @expectedException \InvalidArgumentException + */ public function testInvalidMerge() { - $this->setExpectedException(\InvalidArgumentException::class); (new ElasticWindowSettings())->merge(M::mock(ThrottleSettingsInterface::class)); } diff --git a/tests/Throttle/Settings/FixedWindowSettingsTest.php b/tests/Throttle/Settings/FixedWindowSettingsTest.php index 7ea443d..d30c661 100644 --- a/tests/Throttle/Settings/FixedWindowSettingsTest.php +++ b/tests/Throttle/Settings/FixedWindowSettingsTest.php @@ -2,7 +2,6 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Settings; -use Mockery as M; use Sunspikes\Ratelimit\Throttle\Settings\FixedWindowSettings; class FixedWindowSettingsTest extends AbstractWindowSettingsTest diff --git a/tests/Throttle/Settings/LeakyBucketSettingsTest.php b/tests/Throttle/Settings/LeakyBucketSettingsTest.php index ebf18bf..a08cfe6 100644 --- a/tests/Throttle/Settings/LeakyBucketSettingsTest.php +++ b/tests/Throttle/Settings/LeakyBucketSettingsTest.php @@ -3,10 +3,11 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Settings; use Mockery as M; +use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Throttle\Settings\LeakyBucketSettings; use Sunspikes\Ratelimit\Throttle\Settings\ThrottleSettingsInterface; -class LeakyBucketSettingsTest extends \PHPUnit_Framework_TestCase +class LeakyBucketSettingsTest extends TestCase { public function testMergeWithEmpty() { @@ -30,9 +31,11 @@ public function testMergeWithNonEmpty() self::assertEquals(null, $mergedSettings->getCacheTtl()); } + /** + * @expectedException \InvalidArgumentException + */ public function testInvalidMerge() { - $this->setExpectedException(\InvalidArgumentException::class); (new LeakyBucketSettings())->merge(M::mock(ThrottleSettingsInterface::class)); } diff --git a/tests/Throttle/Settings/MovingWindowSettingsTest.php b/tests/Throttle/Settings/MovingWindowSettingsTest.php index e2e16bd..9eae1e5 100644 --- a/tests/Throttle/Settings/MovingWindowSettingsTest.php +++ b/tests/Throttle/Settings/MovingWindowSettingsTest.php @@ -2,7 +2,6 @@ namespace Sunspikes\Tests\Ratelimit\Throttle\Settings; -use Mockery as M; use Sunspikes\Ratelimit\Throttle\Settings\MovingWindowSettings; class MovingWindowSettingsTest extends AbstractWindowSettingsTest From 1e9dfb72ca114e6a0de6352546cf8482537ee68a Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Wed, 8 Nov 2017 16:18:48 +0100 Subject: [PATCH 5/6] Run the functional tests on redis --- .travis.yml | 4 ++ src/Cache/ThrottlerCache.php | 4 -- .../Functional/AbstractThrottlerTestCase.php | 51 +++++++++++++++---- tests/Functional/MovingWindowTest.php | 3 +- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7362847..7dd92f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,14 @@ language: php +services: + - redis-server + php: - 7.0 - 7.1 before_script: + - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - composer self-update - composer install --prefer-source --dev diff --git a/src/Cache/ThrottlerCache.php b/src/Cache/ThrottlerCache.php index 1170de2..c08d38e 100644 --- a/src/Cache/ThrottlerCache.php +++ b/src/Cache/ThrottlerCache.php @@ -88,10 +88,6 @@ public function setItem(string $key, ThrottlerItemInterface $item): bool { try { $cacheItem = $this->cacheItemPool->getItem($key); - - if ($cacheItem->isHit()) { - $this->cacheItemPool->deleteItem($key); - } } catch (PsrCacheException $e) { throw new CacheAdapterException($e->getMessage(), $e->getCode(), $e->getPrevious()); } diff --git a/tests/Functional/AbstractThrottlerTestCase.php b/tests/Functional/AbstractThrottlerTestCase.php index 47850de..cb7489c 100644 --- a/tests/Functional/AbstractThrottlerTestCase.php +++ b/tests/Functional/AbstractThrottlerTestCase.php @@ -3,6 +3,7 @@ namespace Sunspikes\Tests\Ratelimit\Functional; use Cache\Adapter\PHPArray\ArrayCachePool; +use Cache\Adapter\Redis\RedisCachePool; use PHPUnit\Framework\TestCase; use Sunspikes\Ratelimit\Cache\ThrottlerCache; use Sunspikes\Ratelimit\Cache\ThrottlerCacheInterface; @@ -15,25 +16,38 @@ abstract class AbstractThrottlerTestCase extends TestCase */ protected $ratelimiter; - /** - * @var array - */ - protected $cache = []; - /** * @inheritdoc */ protected function setUp() { - $pool = new ArrayCachePool(null, $this->cache); + $pool = $this->getCachePool(); $cache = new ThrottlerCache($pool); $this->ratelimiter = $this->createRatelimiter($cache); } + /** + * Get the cache pool adapter to use + * + * @return ArrayCachePool|RedisCachePool + */ + private function getCachePool() + { + if (class_exists(\Redis::class)) { + $redis = new \Redis(); + if (true === $redis->connect('localhost')) { + return new RedisCachePool($redis); + } + } + + return new ArrayCachePool(); + } + public function testThrottlePreLimit() { - $throttle = $this->ratelimiter->get('pre-limit-test'); + $key = $this->getRateLimiterKey('pre-limit-test'); + $throttle = $this->ratelimiter->get($key); for ($i = 0; ++$i < $this->getMaxAttempts();) { $throttle->hit(); @@ -44,7 +58,8 @@ public function testThrottlePreLimit() public function testThrottlePostLimit() { - $throttle = $this->ratelimiter->get('post-limit-test'); + $key = $this->getRateLimiterKey('post-limit-test'); + $throttle = $this->ratelimiter->get($key); for ($i = 0; $i < $this->getMaxAttempts(); $i++) { $throttle->hit(); @@ -55,7 +70,8 @@ public function testThrottlePostLimit() public function testThrottleAccess() { - $throttle = $this->ratelimiter->get('access-test'); + $key = $this->getRateLimiterKey('access-test'); + $throttle = $this->ratelimiter->get($key); for ($i = 0; $i < $this->getMaxAttempts(); $i++) { $throttle->access(); @@ -66,7 +82,8 @@ public function testThrottleAccess() public function testThrottleCount() { - $throttle = $this->ratelimiter->get('count-test'); + $key = $this->getRateLimiterKey('count-test'); + $throttle = $this->ratelimiter->get($key); for ($i = 0; $i < $this->getMaxAttempts(); $i++) { $throttle->access(); @@ -77,13 +94,25 @@ public function testThrottleCount() public function testClear() { - $throttle = $this->ratelimiter->get('clear-test'); + $key = $this->getRateLimiterKey('clear-test'); + $throttle = $this->ratelimiter->get($key); $throttle->hit(); $throttle->clear(); self::assertEquals(0, $throttle->count()); } + /** + * Get an unique key based on throttling mode + * + * @param string $key + * @return string + */ + protected function getRateLimiterKey(string $key): string + { + return $key .'-'. sha1(static::class . mt_rand()); + } + /** * @return int */ diff --git a/tests/Functional/MovingWindowTest.php b/tests/Functional/MovingWindowTest.php index 3d80cde..d3068ae 100644 --- a/tests/Functional/MovingWindowTest.php +++ b/tests/Functional/MovingWindowTest.php @@ -37,7 +37,8 @@ protected function setUp() public function testWindowMoves() { - $throttle = $this->ratelimiter->get('window-moves'); + $key = $this->getRateLimiterKey('window-moves'); + $throttle = $this->ratelimiter->get($key); $timeValues = []; From 982bf1ddc08388a7147c624563dfac69a882d0c1 Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Thu, 9 Nov 2017 10:58:36 +0100 Subject: [PATCH 6/6] Move the interface to abstract class --- src/Cache/AbstractCacheItem.php | 2 +- src/Throttle/Entity/CacheCount.php | 2 +- src/Throttle/Entity/CacheHitMapping.php | 2 +- src/Throttle/Entity/CacheTime.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cache/AbstractCacheItem.php b/src/Cache/AbstractCacheItem.php index 9f90b73..95f6e4b 100644 --- a/src/Cache/AbstractCacheItem.php +++ b/src/Cache/AbstractCacheItem.php @@ -25,7 +25,7 @@ namespace Sunspikes\Ratelimit\Cache; -abstract class AbstractCacheItem +abstract class AbstractCacheItem implements ThrottlerItemInterface { /** * @param string $serialized diff --git a/src/Throttle/Entity/CacheCount.php b/src/Throttle/Entity/CacheCount.php index 923dfc8..010a791 100644 --- a/src/Throttle/Entity/CacheCount.php +++ b/src/Throttle/Entity/CacheCount.php @@ -29,7 +29,7 @@ use Sunspikes\Ratelimit\Cache\AbstractCacheItem; use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; -class CacheCount extends AbstractCacheItem implements ThrottlerItemInterface +class CacheCount extends AbstractCacheItem { /** @var int $count */ private $count; diff --git a/src/Throttle/Entity/CacheHitMapping.php b/src/Throttle/Entity/CacheHitMapping.php index 83e1d29..a3e9dad 100644 --- a/src/Throttle/Entity/CacheHitMapping.php +++ b/src/Throttle/Entity/CacheHitMapping.php @@ -29,7 +29,7 @@ use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; use Sunspikes\Ratelimit\Cache\AbstractCacheItem; -class CacheHitMapping extends AbstractCacheItem implements ThrottlerItemInterface +class CacheHitMapping extends AbstractCacheItem { /** @var array $hitMapping */ private $hitMapping; diff --git a/src/Throttle/Entity/CacheTime.php b/src/Throttle/Entity/CacheTime.php index 73fa51f..d3335f4 100644 --- a/src/Throttle/Entity/CacheTime.php +++ b/src/Throttle/Entity/CacheTime.php @@ -28,7 +28,7 @@ use Sunspikes\Ratelimit\Cache\AbstractCacheItem; use Sunspikes\Ratelimit\Cache\ThrottlerItemInterface; -class CacheTime extends AbstractCacheItem implements ThrottlerItemInterface +class CacheTime extends AbstractCacheItem { /** @var float $limit */ private $time;