diff --git a/composer.json b/composer.json index dc94ca5..007d026 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.*.*" + "utopia-php/database": "0.*.*", + "utopia-php/pools": "^0.8.2" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 574df80..4c257f6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "de838692bf6a19165c97e1d9fa5ce2bf", + "content-hash": "e1fdf34468e708cbcd5c85d3a9ee4ea3", "packages": [ { "name": "brick/math", @@ -2247,6 +2247,58 @@ }, "time": "2023-09-01T17:25:28+00:00" }, + { + "name": "utopia-php/pools", + "version": "0.8.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/pools.git", + "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", + "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "utopia-php/telemetry": "0.1.*" + }, + "require-dev": { + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Pools\\": "src/Pools" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "A simple library to manage connection pools", + "keywords": [ + "framework", + "php", + "pools", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/pools/issues", + "source": "https://github.com/utopia-php/pools/tree/0.8.2" + }, + "time": "2025-04-17T02:04:54+00:00" + }, { "name": "utopia-php/telemetry", "version": "0.1.0", diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index d0bd655..8140279 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -79,6 +79,39 @@ public function remaining(): int return (0 > $left) ? 0 : $left; } + + /** + * Set key + * @param string $key + * @return static + */ + public function setKey(string $key): static + { + $this->key = $key; + return $this; + } + + /** + * Set limit + * @param int $limit + * @return static + */ + public function setLimit(int $limit): static + { + $this->limit = $limit; + return $this; + } + + /** + * Get key + * @return string + */ + public function getKey(): string + { + return $this->key; + } + + /** * Limit * diff --git a/src/Abuse/Adapters/TimeLimit/PoolRedis.php b/src/Abuse/Adapters/TimeLimit/PoolRedis.php new file mode 100644 index 0000000..e82a5b5 --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/PoolRedis.php @@ -0,0 +1,88 @@ + + */ + protected UtopiaPool $pool; + + /** + * @param string $key + * @param int $limit + * @param int $seconds + * @param UtopiaPool $pool The pool to use for connections. Must contain instances of TimeLimit. + * + * @throws \Exception + */ + public function __construct(string $key, int $limit, int $seconds, UtopiaPool $pool) + { + $this->pool = $pool; + $this->key = $key; + $this->limit = $limit; + $this->ttl = $seconds; + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); + + $this->pool->use(function (mixed $resource) { + if (! ($resource instanceof Redis)) { + throw new \Exception('Pool must contain instances of '.Redis::class); + } + }); + } + + /** + * Forward method calls to the internal adapter instance via the pool. + * + * Required because __call() can't be used to implement abstract methods. + * + * @param string $method + * @param array $args + * @return mixed + */ + public function delegate(string $method, array $args): mixed + { + return $this->pool->use(function (Redis $redis) use ($method, $args) { + $this->redis = $redis; + return parent::{$method}(...$args); + }); + } + + protected function count(string $key, int $timestamp): int + { + /** + * @var int $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; + } + + protected function hit(string $key, int $timestamp): void + { + $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function cleanup(int $timestamp): bool + { + /** + * @var bool $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; + } + + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + /** + * @var array $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; + } +} diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 1960261..640be2c 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -41,19 +41,15 @@ protected function count(string $key, int $timestamp): int return 0; } - if (! \is_null($this->count)) { // Get fetched result - return $this->count; - } - /** @var string $count */ $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); if (!$count) { - $this->count = 0; + $count = 0; } else { - $this->count = intval($count); + $count = intval($count); } - return $this->count; + return $count; } /** @@ -73,8 +69,6 @@ protected function hit(string $key, int $timestamp): void ->incr($key) ->expire($key, $this->ttl) ->exec(); - - $this->count = ($this->count ?? 0) + 1; } /** diff --git a/tests/Abuse/PoolRedisTest.php b/tests/Abuse/PoolRedisTest.php new file mode 100644 index 0000000..b8c09c8 --- /dev/null +++ b/tests/Abuse/PoolRedisTest.php @@ -0,0 +1,29 @@ + $pool + */ + protected static \Utopia\Pools\Pool $pool; + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + + self::$pool = new \Utopia\Pools\Pool('test', 10, function () { + $redis = RedisTest::initialiseRedis(); + return $redis; + }); + } + + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit + { + return new PoolRedis($key, $limit, $seconds, self::$pool); + } +} diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 085da1e..29b979b 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -24,7 +24,7 @@ public static function setUpBeforeClass(): void self::$redis = self::initialiseRedis(); } - private static function initialiseRedis(): \Redis + protected static function initialiseRedis(): \Redis { $redis = new \Redis(); $redis->connect('redis', 6379);