From abc7350bb90788bd1a16d5f097aa88f08b231d30 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 04:39:54 +0000 Subject: [PATCH 01/10] Feat: pool adapter --- composer.json | 3 +- composer.lock | 54 +++++++++++++++++++++++- src/Abuse/Adapters/TimeLimit/Pool.php | 61 +++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/Abuse/Adapters/TimeLimit/Pool.php 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/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php new file mode 100644 index 0000000..58b18c0 --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -0,0 +1,61 @@ + $pool The pool to use for connections. Must contain instances of Adapter. + * + * @throws \Exception + */ + public function __construct(UtopiaPool $pool) + { + $this->pool = $pool; + + $this->pool->use(function (mixed $resource) { + if (! ($resource instanceof TimeLimit)) { + throw new \Exception('Pool must contain instances of '.TimeLimit::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 (TimeLimit $adapter) use ($method, $args) { + return $adapter->{$method}(...$args); + }); + } + + protected function count(string $key, int $timestamp): int + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + protected function hit(string $key, int $timestamp): void + { + $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function cleanup(int $timestamp): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } +} From cc624b58ee1c9f48ed1cf44de84a7a0afdddf824 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 04:48:52 +0000 Subject: [PATCH 02/10] Fix codeql errors --- src/Abuse/Adapters/TimeLimit/Pool.php | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php index 58b18c0..ac07acd 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -5,10 +5,13 @@ class Pool extends TimeLimit { + /** + * @var UtopiaPool + */ protected UtopiaPool $pool; /** - * @param UtopiaPool $pool The pool to use for connections. Must contain instances of Adapter. + * @param UtopiaPool $pool The pool to use for connections. Must contain instances of TimeLimit. * * @throws \Exception */ @@ -41,7 +44,11 @@ public function delegate(string $method, array $args): mixed protected function count(string $key, int $timestamp): int { - return $this->delegate(__FUNCTION__, \func_get_args()); + /** + * @var int $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; } protected function hit(string $key, int $timestamp): void @@ -51,11 +58,19 @@ protected function hit(string $key, int $timestamp): void public function cleanup(int $timestamp): bool { - return $this->delegate(__FUNCTION__, \func_get_args()); + /** + * @var bool $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; } public function getLogs(?int $offset = null, ?int $limit = 25): array { - return $this->delegate(__FUNCTION__, \func_get_args()); + /** + * @var array $result + */ + $result = $this->delegate(__FUNCTION__, \func_get_args()); + return $result; } } From a467996112f5bc9d07677422f947f0897171d682 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 05:49:59 +0000 Subject: [PATCH 03/10] fix linter --- src/Abuse/Adapters/TimeLimit/Pool.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php index ac07acd..ee69fe7 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -5,9 +5,9 @@ class Pool extends TimeLimit { - /** - * @var UtopiaPool - */ + /** + * @var UtopiaPool + */ protected UtopiaPool $pool; /** From 94b9fc4c2a2c16a1019e9c0d1d1eaa1ef28e234b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 05:59:40 +0000 Subject: [PATCH 04/10] Update pool adapter --- src/Abuse/Adapters/TimeLimit/Pool.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php index ee69fe7..9bdf072 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -2,26 +2,38 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Pools\Pool as UtopiaPool; +use Utopia\Abuse\Adapters\TimeLimit\Redis as RedisAdapter; +use Redis; class Pool extends TimeLimit { /** - * @var UtopiaPool + * @var UtopiaPool */ protected UtopiaPool $pool; + protected string $key; + protected int $limit; + protected int $seconds; + /** - * @param UtopiaPool $pool The pool to use for connections. Must contain instances of TimeLimit. + * @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(UtopiaPool $pool) + public function __construct(string $key, int $limit, int $seconds, UtopiaPool $pool) { $this->pool = $pool; + $this->key = $key; + $this->limit = $limit; + $this->seconds = $seconds; $this->pool->use(function (mixed $resource) { - if (! ($resource instanceof TimeLimit)) { - throw new \Exception('Pool must contain instances of '.TimeLimit::class); + if (! ($resource instanceof Redis)) { + throw new \Exception('Pool must contain instances of '.Redis::class); } }); } @@ -37,7 +49,8 @@ public function __construct(UtopiaPool $pool) */ public function delegate(string $method, array $args): mixed { - return $this->pool->use(function (TimeLimit $adapter) use ($method, $args) { + return $this->pool->use(function (Redis $redis) use ($method, $args) { + $adapter = new RedisAdapter($this->key, $this->limit, $this->seconds, $redis); return $adapter->{$method}(...$args); }); } From 1f7a2b3eb6ebb1aad575728c34e5b957e39cb5a8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 06:05:21 +0000 Subject: [PATCH 05/10] Refactor and pool adapter test --- src/Abuse/Adapters/TimeLimit/Pool.php | 2 ++ tests/Abuse/PoolTest.php | 26 ++++++++++++++++++++++++++ tests/Abuse/RedisTest.php | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/Abuse/PoolTest.php diff --git a/src/Abuse/Adapters/TimeLimit/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php index 9bdf072..1623c4f 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -1,5 +1,7 @@ connect('redis', 6379); From 8c31754f048d4b3f498e931bf7d22a97578dbd7d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 12:11:25 +0545 Subject: [PATCH 06/10] Update tests/Abuse/PoolTest.php Co-authored-by: Jake Barnby --- tests/Abuse/PoolTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Abuse/PoolTest.php b/tests/Abuse/PoolTest.php index f168380..2234c35 100644 --- a/tests/Abuse/PoolTest.php +++ b/tests/Abuse/PoolTest.php @@ -12,7 +12,6 @@ public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - self::$pool = new \Utopia\Pools\Pool('test', 10, function () { $redis = RedisTest::initialiseRedis(); return $redis; From 33bb99f736dc60cda25498853f14ed365fa1d14c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 20 May 2025 07:14:55 +0000 Subject: [PATCH 07/10] refactor pools adapter and test --- src/Abuse/Adapters/TimeLimit.php | 33 ++++++++++++++++++++++++++ src/Abuse/Adapters/TimeLimit/Pool.php | 31 ++++++++++++------------ src/Abuse/Adapters/TimeLimit/Redis.php | 18 ++++++++++++++ tests/Abuse/PoolTest.php | 7 +++++- 4 files changed, 72 insertions(+), 17 deletions(-) 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/Pool.php b/src/Abuse/Adapters/TimeLimit/Pool.php index 1623c4f..32253bd 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/Pool.php @@ -5,38 +5,34 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Pools\Pool as UtopiaPool; use Utopia\Abuse\Adapters\TimeLimit\Redis as RedisAdapter; -use Redis; -class Pool extends TimeLimit +class Pool extends RedisAdapter { /** - * @var UtopiaPool + * @var UtopiaPool */ protected UtopiaPool $pool; - protected string $key; - protected int $limit; - protected int $seconds; - /** * @param string $key * @param int $limit * @param int $seconds - * @param UtopiaPool $pool The pool to use for connections. Must contain instances of TimeLimit. + * @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->seconds = $seconds; - $this->pool->use(function (mixed $resource) { - if (! ($resource instanceof Redis)) { - throw new \Exception('Pool must contain instances of '.Redis::class); + $this->pool->use(function (mixed $adapter) use ($key, $limit, $seconds) { + if (! ($adapter instanceof RedisAdapter)) { + throw new \Exception('Pool must contain instances of '.RedisAdapter::class); } + + $this->setKey($key); + $this->setLimit($limit); + $this->setTtl($seconds); }); } @@ -51,8 +47,11 @@ public function __construct(string $key, int $limit, int $seconds, UtopiaPool $p */ public function delegate(string $method, array $args): mixed { - return $this->pool->use(function (Redis $redis) use ($method, $args) { - $adapter = new RedisAdapter($this->key, $this->limit, $this->seconds, $redis); + return $this->pool->use(function (RedisAdapter $adapter) use ($method, $args) { + $adapter + ->setTtl($this->getTtl()) + ->setKey($this->getKey()) + ->setLimit($this->limit()); return $adapter->{$method}(...$args); }); } diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 1960261..1754663 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -113,4 +113,22 @@ public function cleanup(int $timestamp): bool // No need for manual cleanup - Redis TTL handles this automatically return true; } + + /** + * set ttl + * @param int $seconds + * @return static + */ + public function setTtl(int $seconds): static + { + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); + $this->ttl = $seconds; + return $this; + } + + public function getTtl(): int + { + return $this->ttl; + } } diff --git a/tests/Abuse/PoolTest.php b/tests/Abuse/PoolTest.php index 2234c35..92098f3 100644 --- a/tests/Abuse/PoolTest.php +++ b/tests/Abuse/PoolTest.php @@ -3,18 +3,23 @@ namespace Utopia\Tests; use Utopia\Abuse\Adapters\TimeLimit\Pool; +use Utopia\Abuse\Adapters\TimeLimit\Redis; use Utopia\Abuse\Adapters\TimeLimit; class PoolTest extends RedisTest { + /** + * @var \Utopia\Pools\Pool $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; + return new Redis('', 10, 60, $redis); }); } From ed5f11a65cf1c95053ce7cd40cbe0e3623566220 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 May 2025 05:29:34 +0000 Subject: [PATCH 08/10] PoolRedis adapter --- .../TimeLimit/{Pool.php => PoolRedis.php} | 31 +++++++++---------- src/Abuse/Adapters/TimeLimit/Redis.php | 30 ++---------------- 2 files changed, 17 insertions(+), 44 deletions(-) rename src/Abuse/Adapters/TimeLimit/{Pool.php => PoolRedis.php} (69%) diff --git a/src/Abuse/Adapters/TimeLimit/Pool.php b/src/Abuse/Adapters/TimeLimit/PoolRedis.php similarity index 69% rename from src/Abuse/Adapters/TimeLimit/Pool.php rename to src/Abuse/Adapters/TimeLimit/PoolRedis.php index 32253bd..11512a0 100644 --- a/src/Abuse/Adapters/TimeLimit/Pool.php +++ b/src/Abuse/Adapters/TimeLimit/PoolRedis.php @@ -1,15 +1,14 @@ + * @var UtopiaPool */ protected UtopiaPool $pool; @@ -17,22 +16,23 @@ class Pool extends RedisAdapter * @param string $key * @param int $limit * @param int $seconds - * @param UtopiaPool $pool The pool to use for connections. Must contain instances of TimeLimit. + * @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 $adapter) use ($key, $limit, $seconds) { - if (! ($adapter instanceof RedisAdapter)) { - throw new \Exception('Pool must contain instances of '.RedisAdapter::class); + $this->pool->use(function (mixed $resource) { + if (! ($resource instanceof Redis)) { + throw new \Exception('Pool must contain instances of '.Redis::class); } - - $this->setKey($key); - $this->setLimit($limit); - $this->setTtl($seconds); }); } @@ -47,12 +47,9 @@ public function __construct(string $key, int $limit, int $seconds, UtopiaPool $p */ public function delegate(string $method, array $args): mixed { - return $this->pool->use(function (RedisAdapter $adapter) use ($method, $args) { - $adapter - ->setTtl($this->getTtl()) - ->setKey($this->getKey()) - ->setLimit($this->limit()); - return $adapter->{$method}(...$args); + return $this->pool->use(function (Redis $redis) use ($method, $args) { + $this->redis = $redis; + return parent::{$method}(...$args); }); } diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 1754663..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; } /** @@ -113,22 +107,4 @@ public function cleanup(int $timestamp): bool // No need for manual cleanup - Redis TTL handles this automatically return true; } - - /** - * set ttl - * @param int $seconds - * @return static - */ - public function setTtl(int $seconds): static - { - $now = \time(); - $this->timestamp = (int)($now - ($now % $seconds)); - $this->ttl = $seconds; - return $this; - } - - public function getTtl(): int - { - return $this->ttl; - } } From fa3c8717ff106ec7ad8acb137168d7e44a58d204 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 May 2025 05:31:54 +0000 Subject: [PATCH 09/10] Fix test as needed --- src/Abuse/Adapters/TimeLimit/PoolRedis.php | 5 +++-- tests/Abuse/{PoolTest.php => PoolRedisTest.php} | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename tests/Abuse/{PoolTest.php => PoolRedisTest.php} (69%) diff --git a/src/Abuse/Adapters/TimeLimit/PoolRedis.php b/src/Abuse/Adapters/TimeLimit/PoolRedis.php index 11512a0..e82a5b5 100644 --- a/src/Abuse/Adapters/TimeLimit/PoolRedis.php +++ b/src/Abuse/Adapters/TimeLimit/PoolRedis.php @@ -1,11 +1,12 @@ diff --git a/tests/Abuse/PoolTest.php b/tests/Abuse/PoolRedisTest.php similarity index 69% rename from tests/Abuse/PoolTest.php rename to tests/Abuse/PoolRedisTest.php index 92098f3..bbe2ca5 100644 --- a/tests/Abuse/PoolTest.php +++ b/tests/Abuse/PoolRedisTest.php @@ -2,11 +2,10 @@ namespace Utopia\Tests; -use Utopia\Abuse\Adapters\TimeLimit\Pool; -use Utopia\Abuse\Adapters\TimeLimit\Redis; +use Utopia\Abuse\Adapters\TimeLimit\PoolRedis; use Utopia\Abuse\Adapters\TimeLimit; -class PoolTest extends RedisTest +class PoolRedisTest extends RedisTest { /** * @var \Utopia\Pools\Pool $pool @@ -19,12 +18,12 @@ public static function setUpBeforeClass(): void self::$pool = new \Utopia\Pools\Pool('test', 10, function () { $redis = RedisTest::initialiseRedis(); - return new Redis('', 10, 60, $redis); + return $redis; }); } public function getAdapter(string $key, int $limit, int $seconds): TimeLimit { - return new Pool($key, $limit, $seconds, self::$pool); + return new PoolRedis($key, $limit, $seconds, self::$pool); } } From b999d308c1d306fb69215d7d6d66402d4bf0e2b4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 21 May 2025 05:32:18 +0000 Subject: [PATCH 10/10] fix check --- tests/Abuse/PoolRedisTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Abuse/PoolRedisTest.php b/tests/Abuse/PoolRedisTest.php index bbe2ca5..b8c09c8 100644 --- a/tests/Abuse/PoolRedisTest.php +++ b/tests/Abuse/PoolRedisTest.php @@ -8,7 +8,7 @@ class PoolRedisTest extends RedisTest { /** - * @var \Utopia\Pools\Pool $pool + * @var \Utopia\Pools\Pool $pool */ protected static \Utopia\Pools\Pool $pool; public static function setUpBeforeClass(): void