diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b73e9cd..a11537e 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -6,7 +6,7 @@ on: jobs: sanity-check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 env: key: cache-v1 extensions: apc, redis, apcu, memcache, memcached @@ -18,7 +18,7 @@ jobs: id: extcache uses: shivammathur/cache-extensions@v1 with: - php-version: 7.4 + php-version: 8.0 extensions: ${{ env.extensions }} key: ${{ env.key }} @@ -32,7 +32,7 @@ jobs: - name: Setup PHP {{ matrix.php-versions }} uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 tools: composer extensions: ${{ env.extensions }} ini-values: apc.enable_cli=1 @@ -54,7 +54,7 @@ jobs: unit-test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [sanity-check] env: key: cache-v1 @@ -72,7 +72,7 @@ jobs: - 11211:11211 strategy: matrix: - php-versions: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2' ] + php-versions: [ '8.0', '8.1', '8.2', '8.3' ] steps: diff --git a/.php_cs.dist b/.php_cs.dist deleted file mode 100644 index 7a7ee91..0000000 --- a/.php_cs.dist +++ /dev/null @@ -1,11 +0,0 @@ -exclude('vendor') - ->in(__DIR__); - -return PhpCsFixer\Config::create() - ->setFinder($finder) - ->setRules(array( - '@PSR2' => true, - )); diff --git a/README.md b/README.md index 6878edb..3aa02bf 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![GitHub Build](https://github.com/DavidGoodwin/RateLimit/actions/workflows/php.yml/badge.svg)](https://github.com/DavidGoodwin/RateLimit/actions/workflows/php.yml) -[![Travis Build](https://travis-ci.org/DavidGoodwin/RateLimit.svg)](https://travis-ci.org/DavidGoodwin/RateLimit/) - [![Coverage Status](https://coveralls.io/repos/github/DavidGoodwin/RateLimit/badge.svg?branch=master)](https://coveralls.io/github/DavidGoodwin/RateLimit?branch=master) PHP Rate Limiting Library With Token Bucket Algorithm with minimal external dependencies. diff --git a/composer.json b/composer.json index 885c3a3..13ffb62 100644 --- a/composer.json +++ b/composer.json @@ -3,18 +3,18 @@ "name": "palepurple/rate-limit", "description": "PHP rate limiting library with Token Bucket Algorithm, originally touhonoob/rate-limit", "require": { - "php": ">= 5.6" + "php": ">= 8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.7", "php-parallel-lint/php-parallel-lint": "^1.0", "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^8", - "predis/predis": "^1.1", + "phpunit/phpunit": "^8|^9", + "predis/predis": "^1.1|2.0", "psr/cache": "^1.0", - "tedivm/stash": "^0.16", + "tedivm/stash": "^0.16|^1.0", "psalm/phar": "*", - "phpstan/phpstan": "*" + "phpstan/phpstan": "*", + "slevomat/coding-standard": "^8.15" }, "suggest": { "tedivm/stash": "^0.15", @@ -45,8 +45,13 @@ "psalm" : "@php ./vendor/bin/psalm.phar src", "phpstan" : "@php vendor/bin/phpstan analyse --level 6 src", "lint": "@php ./vendor/bin/parallel-lint --exclude vendor/ .", - "check-format": "@php ./vendor/bin/php-cs-fixer fix --ansi --dry-run --diff", - "format": "@php ./vendor/bin/php-cs-fixer fix --ansi", + "check-format": "@php ./vendor/bin/phpcs src", + "format": "@php ./vendor/bin/phpcbf src", "test": "@php ./vendor/bin/phpunit" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..4b882c1 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/psalm.xml b/psalm.xml index 97b2bd3..186ea8b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -21,22 +21,6 @@ - - - - - - - - - - - - - - - - diff --git a/src/Adapter.php b/src/Adapter.php index 228262c..981ee0e 100644 --- a/src/Adapter.php +++ b/src/Adapter.php @@ -14,23 +14,18 @@ abstract class Adapter * @param float $value * @param int $ttl - seconds after which this entry will expire e.g 50 */ - abstract public function set($key, $value, $ttl); - - /** - * @param string $key - * @return float - */ - abstract public function get($key); - + abstract public function set(string $key, float $value, int $ttl); + /** * @param string $key - * @return bool + * @return float - the amount of request allowance left */ - abstract public function exists($key); - + abstract public function get(string $key): float; + + abstract public function exists(string $key): bool; + /** - * @return bool - * @param string $key + * @return bool - true if delete works */ - abstract public function del($key); + abstract public function del(string $key): bool; } diff --git a/src/Adapter/APC.php b/src/Adapter/APC.php index 703af00..858f919 100644 --- a/src/Adapter/APC.php +++ b/src/Adapter/APC.php @@ -10,22 +10,22 @@ */ class APC extends Adapter { - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl) { return apc_store($key, $value, $ttl); } - public function get($key) + public function get(string $key): float { - return apc_fetch($key); + return (float) apc_fetch($key); } - public function exists($key) + public function exists(string $key): bool { return apc_exists($key); } - public function del($key) + public function del(string $key): bool { return apc_delete($key); } diff --git a/src/Adapter/APCu.php b/src/Adapter/APCu.php index 38b74c3..8a54d49 100644 --- a/src/Adapter/APCu.php +++ b/src/Adapter/APCu.php @@ -8,22 +8,22 @@ */ class APCu extends \PalePurple\RateLimit\Adapter { - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl): bool { return apcu_store($key, $value, $ttl); } - public function get($key) + public function get(string $key): float { return apcu_fetch($key); } - public function exists($key) + public function exists(string $key): bool { return apcu_exists($key); } - public function del($key) + public function del(string $key): bool { return apcu_delete($key); } diff --git a/src/Adapter/Memcached.php b/src/Adapter/Memcached.php index 4615db1..410949b 100644 --- a/src/Adapter/Memcached.php +++ b/src/Adapter/Memcached.php @@ -4,7 +4,6 @@ class Memcached extends \PalePurple\RateLimit\Adapter { - /** * @var \Memcached */ @@ -15,37 +14,36 @@ public function __construct(\Memcached $memcached) $this->memcached = $memcached; } - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl): bool { return $this->memcached->set($key, $value, $ttl); } - /** - * @return float - * @param string $key - */ - public function get($key) + public function get(string $key): float { - $val = $this->_get($key); - return (float) $val; + $ret = $this->realGet($key); + if (is_float($ret)) { + return $ret; + } + throw new \InvalidArgumentException("Unexpected data type from memcache, expected float, got " . gettype($ret)); } - /** - * @return bool|float - * @param string $key - */ - private function _get($key) + private function realGet(string $key): bool|float { - return $this->memcached->get($key); + $ret = $this->memcached->get($key); + if (is_float($ret) || is_bool($ret)) { + return $ret; + } + throw new \InvalidArgumentException("Unsupported data type from memcache: " . gettype($ret)); } - public function exists($key) + public function exists(string $key): bool { - $val = $this->_get($key); + $val = $this->realGet($key); return $val !== false; } - public function del($key) + public function del(string $key): bool { return $this->memcached->delete($key); } diff --git a/src/Adapter/Predis.php b/src/Adapter/Predis.php index cf575ad..dc179db 100644 --- a/src/Adapter/Predis.php +++ b/src/Adapter/Predis.php @@ -7,44 +7,30 @@ */ class Predis extends \PalePurple\RateLimit\Adapter { - - /** - * @var \Predis\ClientInterface - */ - protected $redis; + protected \Predis\ClientInterface $redis; public function __construct(\Predis\ClientInterface $client) { $this->redis = $client; } - - /** - * @param string $key - * @param float $value - * @param int $ttl - * @return bool - */ - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl): bool { - return $this->redis->set($key, (string) $value, "ex", $ttl); + $this->redis->set($key, (string)$value, "ex", $ttl); + return true; } - /** - * @return float - * @param string $key - */ - public function get($key) + public function get(string $key): float { return (float)$this->redis->get($key); } - public function exists($key) + public function exists(string $key): bool { return (bool)$this->redis->exists($key); } - public function del($key) + public function del(string $key): bool { return (bool)$this->redis->del([$key]); } diff --git a/src/Adapter/Redis.php b/src/Adapter/Redis.php index 323512b..b3f4e28 100644 --- a/src/Adapter/Redis.php +++ b/src/Adapter/Redis.php @@ -8,7 +8,6 @@ */ class Redis extends \PalePurple\RateLimit\Adapter { - /** * @var \Redis */ @@ -20,24 +19,18 @@ public function __construct(\Redis $redis) } /** - * @param string $key - * @param float $value - * @param int $ttl - * @return bool * @throws \RedisException */ - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl): bool { $ret = $this->redis->set($key, (string)$value, $ttl); return $ret == true; /* redis returns true OR \Redis (when in multimode). */ } /** - * @return float - * @param string $key * @throws \RedisException */ - public function get($key) + public function get(string $key): float { $ret = $this->redis->get($key); if (is_numeric($ret)) { @@ -47,22 +40,27 @@ public function get($key) } /** - * @param string $key - * @return bool * @throws \RedisException */ - public function exists($key) + public function exists(string $key): bool { return $this->redis->exists($key) == true; } /** - * @param string $key - * @return bool * @throws \RedisException */ - public function del($key) + public function del(string $key): bool { - return $this->redis->del($key) > 0; + $ret = $this->redis->del($key); + if (is_bool($ret)) { + return $ret; + } + + if (is_int($ret)) { + return (bool) $ret; + } + + return false; } } diff --git a/src/Adapter/Stash.php b/src/Adapter/Stash.php index 3d2fef3..fb8282a 100644 --- a/src/Adapter/Stash.php +++ b/src/Adapter/Stash.php @@ -11,18 +11,14 @@ */ class Stash extends Adapter { - - /** - * @var \Stash\Pool - */ - private $pool; + private \Stash\Pool $pool; public function __construct(\Stash\Pool $pool) { $this->pool = $pool; } - public function get($key) + public function get(string $key): float { $item = $this->pool->getItem($key); $item->setInvalidationMethod(Invalidation::OLD); @@ -30,10 +26,10 @@ public function get($key) if ($item->isHit()) { return $item->get(); } - return (float) 0; + return (float)0; } - public function set($key, $value, $ttl) + public function set(string $key, float $value, int $ttl): bool { $item = $this->pool->getItem($key); $item->set($value); @@ -41,14 +37,14 @@ public function set($key, $value, $ttl) return $item->save(); } - public function exists($key) + public function exists(string $key): bool { $item = $this->pool->getItem($key); $item->setInvalidationMethod(Invalidation::OLD); return $item->isHit(); } - public function del($key) + public function del(string $key): bool { return $this->pool->deleteItem($key); } diff --git a/src/RateLimit.php b/src/RateLimit.php index 0998ed5..4fa5136 100644 --- a/src/RateLimit.php +++ b/src/RateLimit.php @@ -8,38 +8,20 @@ */ class RateLimit { + protected string $name; + protected int $maxRequests; + protected int $period; - /** - * - * @var string - */ - protected $name; - - /** - * - * @var int - */ - protected $maxRequests; - - /** - * - * @var int - */ - protected $period; - - /** - * @var Adapter - */ - private $adapter; + private Adapter $adapter; /** * RateLimit constructor. - * @param string $name - prefix used in storage keys. - * @param int $maxRequests + * @param string $name - some unique identifying name for the rate limiter + * @param int $maxRequests how many requests (tokens) in $period before rate limiting kicks in * @param int $period seconds * @param Adapter $adapter - storage adapter */ - public function __construct($name, $maxRequests, $period, Adapter $adapter) + public function __construct(string $name, int $maxRequests, int $period, Adapter $adapter) { $this->name = $name; $this->maxRequests = $maxRequests; @@ -50,11 +32,11 @@ public function __construct($name, $maxRequests, $period, Adapter $adapter) /** * Rate Limiting * http://stackoverflow.com/a/668327/670662 - * @param string $id - * @param float $use + * @param string $id - e.g someone's login, ip address or otherwise thing you wish to possibly throttle + * @param float $use - each call to check uses this many tokens * @return boolean - true if you're within your allowance, false if over allowance */ - public function check($id, $use = 1.0) + public function check(string $id, float $use = 1.0): bool { $rate = $this->maxRequests / $this->period; @@ -93,11 +75,11 @@ public function check($id, $use = 1.0) } /** - * @deprecated use getAllowance() instead. * @param string $id * @return int + * @deprecated use getAllowance() instead. */ - public function getAllow($id) + public function getAllow(string $id): int { return $this->getAllowance($id); } @@ -105,11 +87,9 @@ public function getAllow($id) /** * Get allowance left. - * - * @param string $id * @return int number of requests that can be made before hitting a limit. */ - public function getAllowance($id) + public function getAllowance(string $id): int { $this->check($id, 0.0); @@ -118,70 +98,44 @@ public function getAllowance($id) if (!$this->adapter->exists($a_key)) { return $this->maxRequests; } - return (int) max(0, floor($this->adapter->get($a_key))); + return (int)max(0, floor($this->adapter->get($a_key))); } /** * Purge rate limit record for $id - * @param string $id - * @return void */ - public function purge($id) + public function purge(string $id): void { $this->adapter->del($this->keyTime($id)); $this->adapter->del($this->keyAllow($id)); } - /** - * @return string - * @param string $id - */ - private function keyTime($id) + private function keyTime(string $id): string { return $this->name . ":" . $id . ":time"; } - /** - * @return string - * @param string $id - */ - private function keyAllow($id) + private function keyAllow(string $id): string { return $this->name . ":" . $id . ":allow"; } - /** - * @param string $name - * @return void - */ - public function setName($name) + public function setName(string $name): void { $this->name = $name; } - /** - * @param int $maxRequests - * @return void - */ - public function setMaxRequests($maxRequests) + public function setMaxRequests(int $maxRequests): void { $this->maxRequests = $maxRequests; } - /** - * @param int $period - * @return void - */ - public function setPeriod($period) + public function setPeriod(int $period): void { $this->period = $period; } - /** - * @param Adapter $adapter - * @return void - */ - public function setAdapter(Adapter $adapter) + public function setAdapter(Adapter $adapter): void { $this->adapter = $adapter; }