From 83988797f7648dd32ff823b122f38ed09382bc3d Mon Sep 17 00:00:00 2001 From: eldadfux Date: Sun, 28 Apr 2019 22:11:51 +0300 Subject: [PATCH 001/470] first commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000000..27dde965cc8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# abuse From 715415500e8609436119824cb9f36f021541a374 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Sun, 28 Apr 2019 22:16:58 +0300 Subject: [PATCH 002/470] Added source code --- .gitignore | 3 + LICENSE.md | 20 +++ README.md | 78 ++++++++- composer.json | 24 +++ data/schema.sql | 9 ++ src/Abuse/Abuse.php | 31 ++++ src/Abuse/Adapter.php | 15 ++ src/Abuse/Adapters/ReCaptcha.php | 82 ++++++++++ src/Abuse/Adapters/TimeLimit.php | 265 +++++++++++++++++++++++++++++++ 9 files changed, 526 insertions(+), 1 deletion(-) create mode 100755 .gitignore create mode 100755 LICENSE.md create mode 100755 composer.json create mode 100644 data/schema.sql create mode 100644 src/Abuse/Abuse.php create mode 100644 src/Abuse/Adapter.php create mode 100644 src/Abuse/Adapters/ReCaptcha.php create mode 100644 src/Abuse/Adapters/TimeLimit.php diff --git a/.gitignore b/.gitignore new file mode 100755 index 00000000000..8924c8079af --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.lock +/vendor/ +/.idea/ \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 00000000000..27feb0854cd --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Eldad Fux + +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. diff --git a/README.md b/README.md index 27dde965cc8..de7b197a3eb 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ -# abuse +# Utopia Framework Abuse + +Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. + +Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework. + +## Getting Started + +**Time Limit Abuse** + +The time limit abuse allow each key (action) to be performed [X] times in given time frame. +This adapter uses a MySQL / MariaDB to store usage attempts. Before using it create the table schema as documented ate this repository (./data/schema.sql) + +```php +setNamespace('namespace') // DB table namespace + ->setParam('{{ip}}', '127.0.0.1') +; + +$abuse = new Abuse($adapter); + +// Use vars to resolve adapter key + +if(!$abuse->check()) { + throw new Exception('Service was abused!'); // throw error and return X-Rate limit headers here +} +``` + +**ReCaptcha Abuse** + +The ReCaptcha abuse controller is using Google ReCaptcha service to detect when service is being abused by bots. +To use this adapter you need to create an API key from the Google ReCaptcha service [admin console](https://www.google.com/recaptcha/admin). + +```php +check()) { + throw new Exception('Service was abused!'); // throw error and return X-Rate limit headers here +} +``` + + Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. + If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header. + +## System Requirements + +Utopia Framework requires PHP 7.0 or later. We recommend using the latest PHP version whenever possible. + +## Authors + +**Eldad Fux** + ++ [https://twitter.com/eldadfux](https://twitter.com/eldadfux) ++ [https://github.com/eldadfux](https://github.com/eldadfux) + +## Copyright and license + +The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100755 index 00000000000..33971c84458 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "utopia-php/abuse", + "description": "A simple abuse library to manage application usage limits", + "type": "library", + "keywords": ["php","framework", "upf", "utopia", "abuse"], + "license": "MIT", + "minimum-stability": "stable", + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "autoload": { + "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} + }, + "require": { + "php": ">=7.1", + "ext-pdo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + } +} diff --git a/data/schema.sql b/data/schema.sql new file mode 100644 index 00000000000..1c5c09eee27 --- /dev/null +++ b/data/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE `namespace.abuse.abuse` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `_key` varchar(255) NOT NULL, + `_time` varchar(45) NOT NULL, + `_count` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `unique1` (`_key`,`_time`), + KEY `index1` (`_key`,`_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php new file mode 100644 index 00000000000..41d1d3d760e --- /dev/null +++ b/src/Abuse/Abuse.php @@ -0,0 +1,31 @@ +adapter = $adapter; + } + + /** + * Check + * + * Checks if request is considered abuse or not + * + * @return bool + */ + public function check() + { + return $this->adapter->check(); + } +} \ No newline at end of file diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php new file mode 100644 index 00000000000..8df4af8a790 --- /dev/null +++ b/src/Abuse/Adapter.php @@ -0,0 +1,15 @@ +secret = $secret; + $this->response = $response; + $this->remoteIP = $remoteIP; + } + + /** + * Check + * + * Check if user is human or not + */ + public function check() + { + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = array( + 'secret' => urlencode($this->secret), + 'response' => urlencode($this->response), + 'remoteip' => urlencode($this->remoteIP), + ); + + //open connection + $ch = curl_init(); + + //set the url, number of POST vars, POST data + curl_setopt($ch,CURLOPT_URL, $url); + curl_setopt($ch,CURLOPT_POST, count($fields)); + curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($fields)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + //execute post + $result = json_decode(curl_exec($ch), true); + + //close connection + curl_close($ch); + + return $result['success']; + } +} \ No newline at end of file diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php new file mode 100644 index 00000000000..fb6598717a8 --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit.php @@ -0,0 +1,265 @@ +key = $key; + $this->time = date('U', floor(time() / $time) * $time);; + $this->limit = $limit; + $this->connection = $connection; + } + + /** + * @var string + */ + protected $namespace = ''; + + /** + * Set Namespace + * + * Set namespace to divide different scope of data sets + * + * @param $namespace + * @throws Exception + * @return $this + */ + public function setNamespace($namespace) + { + if(empty($namespace)) { + throw new Exception('Missing namespace'); + } + + $this->namespace = $namespace; + + return $this; + } + + /** + * Get Namespace + * + * Get namespace of current set scope + * + * @throws Exception + * @return string + */ + public function getNamespace() + { + if(empty($this->namespace)) { + throw new Exception('Missing namespace'); + } + + return $this->namespace; + } + + /** + * Set Param + * + * Set custom param for key pattern parsing + * + * @param string $key + * @param $value + * @return TimeLimit + */ + public function setParam($key, $value):self + { + $this->params[$key] = $value; + + return $this; + } + + /** + * Get Params + * + * Return array of all key params + * + * @return array + */ + protected function getParams():array + { + return $this->params; + } + + /** + * Parse key with all custom attached params + * + * @return string + */ + protected function parseKey():string + { + foreach($this->getParams() as $key => $value) { + $this->key = str_replace($key, $value, $this->key); + } + + return $this->key; + } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit + * + * @param string $key + * @param int|string $time + * @return int + * @throws Exception + */ + protected function count(string $key, int $time):int + { + if(0 == $this->limit) { // No limit no point for counting + return 0; + } + + if(!is_null($this->count)) { // Get fetched result + return $this->count; + } + + $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` + WHERE _key = :key AND _time = :time + LIMIT 1; + '); + + $st->bindValue(':key', $key, PDO::PARAM_STR); + $st->bindValue(':time', $time, PDO::PARAM_STR); + + $st->execute(); + + $this->count = (int)$st->fetch()['_count']; + + return $this->count; + } + + /** + * @param string $key + * @param int $time seconds + * @return null + * @throws Exception + */ + protected function hit(string $key, int $time) + { + if(0 == $this->limit) { // No limit no point for counting + return null; + } + + $st = $this->getPDO()->prepare('INSERT INTO `' . $this->getNamespace() . '.abuse.abuse` + SET _key = :key, _time = :time, _count = 1 + ON DUPLICATE KEY UPDATE _count = _count + 1; + '); + + $st->bindValue(':key', $key, PDO::PARAM_STR); + $st->bindValue(':time', $time, PDO::PARAM_STR); + + $st->execute(); + + $this->count++; + } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit. limit 0 is equal to unlimited + * + * @return bool + * @throws Exception + */ + public function check():bool + { + if(0 == $this->limit) { + return false; + } + + $key = $this->parseKey(); + + if($this->limit > $this->count($key, $this->time)) { + $this->hit($key, $this->time); + return false; + } + + return true; + } + + /** + * Remaining + * + * Returns the number of current remaining counts + * + * @return int + * @throws Exception + */ + public function remaining():int + { + $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done + return (0 > $left) ? 0 : $left; + } + + /** + * Limit + * + * Return the limit integer + * + * @return int + */ + public function limit():int + { + return $this->limit; + } + + /** + * Time + * + * Return the unix time + * + * @return int + */ + public function time():int + { + return $this->time; + } + + protected function getPDO():PDO + { + return call_user_func($this->connection); + } +} \ No newline at end of file From 6d18ab428e443682546a9867d2f431feb30d2e5d Mon Sep 17 00:00:00 2001 From: eldadfux Date: Sun, 28 Apr 2019 22:23:57 +0300 Subject: [PATCH 003/470] Updated readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index de7b197a3eb..05e45e9cc93 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ This adapter uses a MySQL / MariaDB to store usage attempts. Before using it cre require_once __DIR__ . '/../../vendor/autoload.php'; -use Abuse\Abuse; -use Abuse\Adapters\TimeLimit; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapters\TimeLimit; // Limit login attempts to 10 time in 5 minutes time frame $adapter = new TimeLimit('login-attempt-from-{{ip}}', 10, (60 * 5), function () {/* init and return PDO connection... */}); @@ -46,8 +46,8 @@ To use this adapter you need to create an API key from the Google ReCaptcha serv require_once __DIR__ . '/../../vendor/autoload.php'; -use Abuse\Abuse; -use Abuse\Adapters\ReCaptcha; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapters\ReCaptcha; // Limit login attempts to 10 time in 5 minutes time frame $adapter = new ReCaptcha('secret-api-key', $_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']); From 5870f3e94a3adec1f09ca27733336ec8f8f2d595 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Sun, 28 Apr 2019 22:34:02 +0300 Subject: [PATCH 004/470] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05e45e9cc93..be27725d97e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Utopia Framework Abuse +# Utopia Abuse Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. From 3cffdefdad18e0109b04c5b4e3440001bfbb2006 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Mon, 29 Apr 2019 00:53:28 +0300 Subject: [PATCH 005/470] Updated readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index be27725d97e..206242863ef 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ Although this library is part of the [Utopia Framework](https://github.com/utopi ## Getting Started +Install using composer: +```bash +composer require utopia-php/abuse +``` + **Time Limit Abuse** The time limit abuse allow each key (action) to be performed [X] times in given time frame. From dce96c3e66041abf5c85cf9b42f0a18afde20a70 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Mon, 29 Apr 2019 17:16:21 +0300 Subject: [PATCH 006/470] Added unit tests --- .travis.yml | 22 ++++++++++++ phpunit.xml | 16 +++++++++ tests/Abuse/AbuseTest.php | 71 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 .travis.yml create mode 100755 phpunit.xml create mode 100755 tests/Abuse/AbuseTest.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..b76b7971759 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: +- 7.1 +- 7.2 +- 7.3 + +services: +- mysql + +notifications: + email: + - team@appwrite.io + +before_install: +- mysql -e 'CREATE DATABASE abuse;' +- mysql -e 'CREATE TABLE abuse.`namespace.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, `_time` varchar(45) NOT NULL, `_count` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `unique1` (`_key`,`_time`), KEY `index1` (`_key`,`_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;' + +before_script: composer install + +script: +- vendor/bin/phpunit --configuration phpunit.xml \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 00000000000..ec39a7edeaf --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + ./tests/ + + + \ No newline at end of file diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php new file mode 100755 index 00000000000..6cb16e73e39 --- /dev/null +++ b/tests/Abuse/AbuseTest.php @@ -0,0 +1,71 @@ + + * @version 1.0 RC4 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PDO; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapters\TimeLimit; + +use PHPUnit\Framework\TestCase; + +class AbuseTest extends TestCase +{ + /** + * @var Abuse + */ + protected $abuse = null; + + public function setUp() + { + // Limit login attempts to 3 time in 5 minutes time frame + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), function () { + $dbHost = '127.0.0.1'; + $dbUser = 'travis'; + $dbPass = ''; + $dbName = 'abuse'; + + $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + PDO::ATTR_TIMEOUT => 5 // Seconds + )); + + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + + return $pdo; + }); + + $adapter + ->setNamespace('namespace') // DB table namespace + ->setParam('{{ip}}', '127.0.0.1') + ; + + $this->abuse = new Abuse($adapter); + } + + public function tearDown() + { + $this->abuse = null; + } + + public function testIsValid() + { + // Use vars to resolve adapter key + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), true); + } +} From 7dbd0e1a373cff4747f60641f9d45116cf02713e Mon Sep 17 00:00:00 2001 From: eldadfux Date: Mon, 29 Apr 2019 17:20:24 +0300 Subject: [PATCH 007/470] Changed db table name --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b76b7971759..2a55c3c7f0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ notifications: before_install: - mysql -e 'CREATE DATABASE abuse;' -- mysql -e 'CREATE TABLE abuse.`namespace.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, `_time` varchar(45) NOT NULL, `_count` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `unique1` (`_key`,`_time`), KEY `index1` (`_key`,`_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;' +- mysql -e 'CREATE TABLE abuse.`namespace.abuse.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, `_time` varchar(45) NOT NULL, `_count` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `unique1` (`_key`,`_time`), KEY `index1` (`_key`,`_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;' before_script: composer install From 1178f80b7f1df15397c74319c30abb3bf96b74e9 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Mon, 29 Apr 2019 17:23:09 +0300 Subject: [PATCH 008/470] Changed db table name --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 206242863ef..b271946d88c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Utopia Abuse +[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.org/utopia-php/abuse) +![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) +![License](https://img.shields.io/github/license/utopia-php/abuse.svg) + Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework. From 4441c922fa43b940d0617f63c18cb5c2d078a560 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Mon, 29 Apr 2019 20:30:29 +0300 Subject: [PATCH 009/470] Updated readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b271946d88c..faca7425688 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,7 @@ if(!$abuse->check()) { } ``` - Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. - If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header. +*Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header.* ## System Requirements From abfb5eaf3a5a4869436691b2fdd525b7d9516075 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Fri, 3 May 2019 19:08:16 +0300 Subject: [PATCH 010/470] Updated CI env setup --- .travis.yml | 3 +-- data/schema.sql | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a55c3c7f0e..cb422f3feaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,7 @@ notifications: - team@appwrite.io before_install: -- mysql -e 'CREATE DATABASE abuse;' -- mysql -e 'CREATE TABLE abuse.`namespace.abuse.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, `_time` varchar(45) NOT NULL, `_count` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `unique1` (`_key`,`_time`), KEY `index1` (`_key`,`_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;' +- mysql < data/schema.sql' before_script: composer install diff --git a/data/schema.sql b/data/schema.sql index 1c5c09eee27..fcf9f3b50f5 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -1,3 +1,5 @@ +CREATE DATABASE abuse; + CREATE TABLE `namespace.abuse.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, From 9443fc5e94532a945ca27af5bbbedd0fe13ea456 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Fri, 3 May 2019 19:16:41 +0300 Subject: [PATCH 011/470] Updated CI env setup --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cb422f3feaf..62f043bbbff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ notifications: - team@appwrite.io before_install: -- mysql < data/schema.sql' +- mysql < data/schema.sql before_script: composer install From 88f03ac5eb60e16657e13fec24afbaae36eb98fb Mon Sep 17 00:00:00 2001 From: eldadfux Date: Fri, 3 May 2019 19:20:03 +0300 Subject: [PATCH 012/470] Updated CI env setup --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 62f043bbbff..52bedbb33a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ notifications: - team@appwrite.io before_install: -- mysql < data/schema.sql +- mysql < source data/schema.sql before_script: composer install From 3a87ae8a36bf15dbf6b91e6c036f82d022b44b54 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Fri, 3 May 2019 19:22:06 +0300 Subject: [PATCH 013/470] Updated CI env setup --- .travis.yml | 2 +- data/schema.sql | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 52bedbb33a5..62f043bbbff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ notifications: - team@appwrite.io before_install: -- mysql < source data/schema.sql +- mysql < data/schema.sql before_script: composer install diff --git a/data/schema.sql b/data/schema.sql index fcf9f3b50f5..4adaed098ca 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -1,5 +1,7 @@ CREATE DATABASE abuse; +USE abuse; + CREATE TABLE `namespace.abuse.abuse` ( `id` int(11) NOT NULL AUTO_INCREMENT, `_key` varchar(255) NOT NULL, From 59a42c08f494ff23d79d79bbf37504a8761274e7 Mon Sep 17 00:00:00 2001 From: eldadfux Date: Wed, 8 May 2019 23:26:59 +0300 Subject: [PATCH 014/470] Added gitter badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index faca7425688..fef134dea33 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.org/utopia-php/abuse) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) -![License](https://img.shields.io/github/license/utopia-php/abuse.svg) +[![Chat With Us](https://img.shields.io/gitter/room/utopia-php/community.svg)](https://gitter.im/utopia-php/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. From a26dfac9cf53c21bda633e381f1875cd93684c19 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 14:59:19 +0200 Subject: [PATCH 015/470] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 62f043bbbff..3851aed43cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - 7.1 - 7.2 - 7.3 +- 7.4 services: - mysql @@ -18,4 +19,4 @@ before_install: before_script: composer install script: -- vendor/bin/phpunit --configuration phpunit.xml \ No newline at end of file +- vendor/bin/phpunit --configuration phpunit.xml From fe30f65366486852800bd6f7bab43f52dfc8dbc3 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:13:13 +0200 Subject: [PATCH 016/470] Update TimeLimit.php --- src/Abuse/Adapters/TimeLimit.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index fb6598717a8..8bed862a942 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -165,8 +165,12 @@ protected function count(string $key, int $time):int $st->bindValue(':time', $time, PDO::PARAM_STR); $st->execute(); - - $this->count = (int)$st->fetch()['_count']; + + $x = $st->fetch(); + $y = $x['_count']; + var_dump($x); + var_dump($y); + $this->count = (int)$y; return $this->count; } @@ -262,4 +266,4 @@ protected function getPDO():PDO { return call_user_func($this->connection); } -} \ No newline at end of file +} From b558e9a310cd45a47649205c9da1a2dcabc1f667 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:22:43 +0200 Subject: [PATCH 017/470] test --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 8bed862a942..67c2a1405b1 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -216,6 +216,7 @@ public function check():bool $key = $this->parseKey(); + return false; if($this->limit > $this->count($key, $this->time)) { $this->hit($key, $this->time); return false; From 252ceb4353b8c74bd467a2685aa4a338cdb98286 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:25:29 +0200 Subject: [PATCH 018/470] test --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 67c2a1405b1..0506ff0d042 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -169,7 +169,8 @@ protected function count(string $key, int $time):int $x = $st->fetch(); $y = $x['_count']; var_dump($x); - var_dump($y); + var_dump($y); + exit(); $this->count = (int)$y; return $this->count; @@ -216,7 +217,6 @@ public function check():bool $key = $this->parseKey(); - return false; if($this->limit > $this->count($key, $this->time)) { $this->hit($key, $this->time); return false; From 74700cf2a16b366c01cf8a8475d10601bcdf5ef2 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:29:19 +0200 Subject: [PATCH 019/470] test --- src/Abuse/Adapters/TimeLimit.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 0506ff0d042..8473f281830 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -152,6 +152,8 @@ protected function count(string $key, int $time):int return 0; } + var_dump('counter'); + if(!is_null($this->count)) { // Get fetched result return $this->count; } @@ -168,10 +170,10 @@ protected function count(string $key, int $time):int $x = $st->fetch(); $y = $x['_count']; - var_dump($x); - var_dump($y); - exit(); - $this->count = (int)$y; + var_dump($x); + var_dump($y); + exit(); + $this->count = (int)$y; return $this->count; } From 48af761107e90817beb78e6acc1a983bbff78ef8 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:31:45 +0200 Subject: [PATCH 020/470] test --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 8473f281830..dcc53d5b6f0 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -152,7 +152,7 @@ protected function count(string $key, int $time):int return 0; } - var_dump('counter'); + var_dump('counter', $this->count); if(!is_null($this->count)) { // Get fetched result return $this->count; From 13a1651b247b114937519063832636ed189196d7 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:33:54 +0200 Subject: [PATCH 021/470] return int --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index dcc53d5b6f0..2d5cdddc50c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -155,7 +155,7 @@ protected function count(string $key, int $time):int var_dump('counter', $this->count); if(!is_null($this->count)) { // Get fetched result - return $this->count; + return 0; } $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` From 50e126366244b40415c569979d862df29695b8cd Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:37:18 +0200 Subject: [PATCH 022/470] test --- src/Abuse/Adapters/TimeLimit.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 2d5cdddc50c..e8eb2d61fa6 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -152,12 +152,12 @@ protected function count(string $key, int $time):int return 0; } - var_dump('counter', $this->count); - if(!is_null($this->count)) { // Get fetched result - return 0; + return $this->count; } - + + var_dump('counter', $this->count); + $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` WHERE _key = :key AND _time = :time LIMIT 1; From 1f77e2d45b1c41879be42d6ffced77179178d63e Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:41:34 +0200 Subject: [PATCH 023/470] test --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e8eb2d61fa6..385db02f161 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -162,6 +162,7 @@ protected function count(string $key, int $time):int WHERE _key = :key AND _time = :time LIMIT 1; '); + var_dump('stamt', $this->count); $st->bindValue(':key', $key, PDO::PARAM_STR); $st->bindValue(':time', $time, PDO::PARAM_STR); From 518a121ab7311581f716b30da63425285d86d037 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:43:47 +0200 Subject: [PATCH 024/470] test --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 385db02f161..528c2d00a4c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -162,13 +162,13 @@ protected function count(string $key, int $time):int WHERE _key = :key AND _time = :time LIMIT 1; '); - var_dump('stamt', $this->count); $st->bindValue(':key', $key, PDO::PARAM_STR); $st->bindValue(':time', $time, PDO::PARAM_STR); $st->execute(); - + +var_dump('stamt', $this->count); $x = $st->fetch(); $y = $x['_count']; var_dump($x); From d4ba24a4f48577a0037effc7e763318ec316f8bc Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:44:18 +0200 Subject: [PATCH 025/470] test --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 528c2d00a4c..2f5999bbe93 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -170,8 +170,8 @@ protected function count(string $key, int $time):int var_dump('stamt', $this->count); $x = $st->fetch(); - $y = $x['_count']; var_dump($x); + $y = $x['_count']; var_dump($y); exit(); $this->count = (int)$y; From ec6e79defc1641de3cf4e271e261f299c79d5be7 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:47:18 +0200 Subject: [PATCH 026/470] test --- src/Abuse/Adapters/TimeLimit.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 2f5999bbe93..5339825b165 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -168,12 +168,14 @@ protected function count(string $key, int $time):int $st->execute(); + print_r($this->getPDO()->errorInfo()); var_dump('stamt', $this->count); $x = $st->fetch(); var_dump($x); $y = $x['_count']; var_dump($y); exit(); + $this->count = (int)$y; return $this->count; From 268f3c9b27279bac0ec649167476756d70dd655f Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 9 Dec 2019 15:51:39 +0200 Subject: [PATCH 027/470] test --- src/Abuse/Adapters/TimeLimit.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 5339825b165..7112fe81c34 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -155,9 +155,7 @@ protected function count(string $key, int $time):int if(!is_null($this->count)) { // Get fetched result return $this->count; } - - var_dump('counter', $this->count); - + $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` WHERE _key = :key AND _time = :time LIMIT 1; @@ -168,15 +166,10 @@ protected function count(string $key, int $time):int $st->execute(); - print_r($this->getPDO()->errorInfo()); -var_dump('stamt', $this->count); - $x = $st->fetch(); - var_dump($x); - $y = $x['_count']; - var_dump($y); - exit(); + $result = $st->fetch(); + $result = (is_array($result) && isset($result['_count'])) ? $result['_count'] : 0; - $this->count = (int)$y; + $this->count = (int)$result; return $this->count; } From 8a014449fe92b5275ef5006ac7f7ecaaec23e64d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 28 Dec 2019 21:43:47 +0200 Subject: [PATCH 028/470] Updated Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fef134dea33..d5a36bb5623 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.org/utopia-php/abuse) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) -[![Chat With Us](https://img.shields.io/gitter/room/utopia-php/community.svg)](https://gitter.im/utopia-php/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) +[![Discord](https://img.shields.io/discord/564160730845151244)](https://discord.gg/GSeTUeA) Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. -Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework. +Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free, and can be used as standalone with any other PHP project or framework. ## Getting Started From b485eddeda335c4f7d1a16fbf5544e37bdf195ac Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 20 Jun 2020 14:35:08 +0300 Subject: [PATCH 029/470] Optimizing PHP performance by using fully-qualified function calls --- src/Abuse/Adapters/ReCaptcha.php | 20 ++++++++++---------- src/Abuse/Adapters/TimeLimit.php | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index d2dede1b7e8..afdc44fe12d 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -57,25 +57,25 @@ public function check() { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array( - 'secret' => urlencode($this->secret), - 'response' => urlencode($this->response), - 'remoteip' => urlencode($this->remoteIP), + 'secret' => \urlencode($this->secret), + 'response' => \urlencode($this->response), + 'remoteip' => \urlencode($this->remoteIP), ); //open connection - $ch = curl_init(); + $ch = \curl_init(); //set the url, number of POST vars, POST data - curl_setopt($ch,CURLOPT_URL, $url); - curl_setopt($ch,CURLOPT_POST, count($fields)); - curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($fields)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + \curl_setopt($ch,CURLOPT_URL, $url); + \curl_setopt($ch,CURLOPT_POST, \count($fields)); + \curl_setopt($ch,CURLOPT_POSTFIELDS, \http_build_query($fields)); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //execute post - $result = json_decode(curl_exec($ch), true); + $result = \json_decode(\curl_exec($ch), true); //close connection - curl_close($ch); + \curl_close($ch); return $result['success']; } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 7112fe81c34..52303fb31df 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -47,7 +47,7 @@ class TimeLimit implements Adapter public function __construct(string $key, int $limit, int $time, callable $connection) { $this->key = $key; - $this->time = date('U', floor(time() / $time) * $time);; + $this->time = \date('U', \floor(\time() / $time) * $time);; $this->limit = $limit; $this->connection = $connection; } @@ -130,7 +130,7 @@ protected function getParams():array protected function parseKey():string { foreach($this->getParams() as $key => $value) { - $this->key = str_replace($key, $value, $this->key); + $this->key = \str_replace($key, $value, $this->key); } return $this->key; @@ -152,7 +152,7 @@ protected function count(string $key, int $time):int return 0; } - if(!is_null($this->count)) { // Get fetched result + if(!\is_null($this->count)) { // Get fetched result return $this->count; } @@ -167,7 +167,7 @@ protected function count(string $key, int $time):int $st->execute(); $result = $st->fetch(); - $result = (is_array($result) && isset($result['_count'])) ? $result['_count'] : 0; + $result = (\is_array($result) && isset($result['_count'])) ? $result['_count'] : 0; $this->count = (int)$result; @@ -263,6 +263,6 @@ public function time():int protected function getPDO():PDO { - return call_user_func($this->connection); + return \call_user_func($this->connection); } } From 26d99d1781cae798b7767b0f073deb744446d14d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:45:33 +0300 Subject: [PATCH 030/470] Updated tests --- .travis.yml | 1 + composer.json | 3 ++- psalm.xml | 15 +++++++++++ src/Abuse/Adapters/ReCaptcha.php | 2 +- src/Abuse/Adapters/TimeLimit.php | 46 +++++++++++++++++--------------- tests/Abuse/AbuseTest.php | 4 +-- 6 files changed, 46 insertions(+), 25 deletions(-) create mode 100644 psalm.xml diff --git a/.travis.yml b/.travis.yml index 3851aed43cf..d7082016fdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ before_script: composer install script: - vendor/bin/phpunit --configuration phpunit.xml +- vendor/bin/psalm diff --git a/composer.json b/composer.json index 33971c84458..cad6f336716 100755 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "ext-pdo": "*" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.4", + "vimeo/psalm": "4.0.1" } } diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000000..7c0333df3b9 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index afdc44fe12d..7321605d2b9 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -72,7 +72,7 @@ public function check() \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //execute post - $result = \json_decode(\curl_exec($ch), true); + $result = \json_decode((string)\curl_exec($ch), true); //close connection \curl_close($ch); diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 52303fb31df..ba466605e5c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -29,7 +29,7 @@ class TimeLimit implements Adapter protected $limit = 0; /** - * @var int + * @var int|null */ protected $count = null; @@ -47,7 +47,7 @@ class TimeLimit implements Adapter public function __construct(string $key, int $limit, int $time, callable $connection) { $this->key = $key; - $this->time = \date('U', \floor(\time() / $time) * $time);; + $this->time = (int)\date('U', (int)(\floor(\time() / $time)) * $time); $this->limit = $limit; $this->connection = $connection; } @@ -62,11 +62,11 @@ public function __construct(string $key, int $limit, int $time, callable $connec * * Set namespace to divide different scope of data sets * - * @param $namespace + * @param string $namespace * @throws Exception * @return $this */ - public function setNamespace($namespace) + public function setNamespace(string $namespace): self { if(empty($namespace)) { throw new Exception('Missing namespace'); @@ -85,7 +85,7 @@ public function setNamespace($namespace) * @throws Exception * @return string */ - public function getNamespace() + public function getNamespace(): string { if(empty($this->namespace)) { throw new Exception('Missing namespace'); @@ -100,10 +100,11 @@ public function getNamespace() * Set custom param for key pattern parsing * * @param string $key - * @param $value - * @return TimeLimit + * @param string $value + * + * @return $this */ - public function setParam($key, $value):self + public function setParam(string $key, string $value): self { $this->params[$key] = $value; @@ -117,7 +118,7 @@ public function setParam($key, $value):self * * @return array */ - protected function getParams():array + protected function getParams(): array { return $this->params; } @@ -127,7 +128,7 @@ protected function getParams():array * * @return string */ - protected function parseKey():string + protected function parseKey(): string { foreach($this->getParams() as $key => $value) { $this->key = \str_replace($key, $value, $this->key); @@ -142,11 +143,11 @@ protected function parseKey():string * Checks if number of counts is bigger or smaller than current limit * * @param string $key - * @param int|string $time + * @param int $time * @return int * @throws Exception */ - protected function count(string $key, int $time):int + protected function count(string $key, int $time): int { if(0 == $this->limit) { // No limit no point for counting return 0; @@ -177,13 +178,15 @@ protected function count(string $key, int $time):int /** * @param string $key * @param int $time seconds - * @return null + * * @throws Exception + * + * @return void */ - protected function hit(string $key, int $time) + protected function hit(string $key, int $time): void { if(0 == $this->limit) { // No limit no point for counting - return null; + return; } $st = $this->getPDO()->prepare('INSERT INTO `' . $this->getNamespace() . '.abuse.abuse` @@ -207,7 +210,7 @@ protected function hit(string $key, int $time) * @return bool * @throws Exception */ - public function check():bool + public function check(): bool { if(0 == $this->limit) { return false; @@ -228,10 +231,11 @@ public function check():bool * * Returns the number of current remaining counts * - * @return int * @throws Exception + * + * @return int */ - public function remaining():int + public function remaining(): int { $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done return (0 > $left) ? 0 : $left; @@ -244,7 +248,7 @@ public function remaining():int * * @return int */ - public function limit():int + public function limit(): int { return $this->limit; } @@ -256,12 +260,12 @@ public function limit():int * * @return int */ - public function time():int + public function time(): int { return $this->time; } - protected function getPDO():PDO + protected function getPDO(): PDO { return \call_user_func($this->connection); } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 6cb16e73e39..9a9e63ec10c 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -26,7 +26,7 @@ class AbuseTest extends TestCase */ protected $abuse = null; - public function setUp() + public function setUp():void { // Limit login attempts to 3 time in 5 minutes time frame $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), function () { @@ -55,7 +55,7 @@ public function setUp() $this->abuse = new Abuse($adapter); } - public function tearDown() + public function tearDown():void { $this->abuse = null; } From 629a95479c45647d04f0eba9bbc606e835e4ed5e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:46:07 +0300 Subject: [PATCH 031/470] Added --show-info=true flag --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d7082016fdd..ad494adb3ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ before_script: composer install script: - vendor/bin/phpunit --configuration phpunit.xml -- vendor/bin/psalm +- vendor/bin/psalm --show-info=true From 6726e349989914c84c351110ce2e8838c59ba965 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:46:35 +0300 Subject: [PATCH 032/470] Added php8 tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ad494adb3ac..6cdf1d50421 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.2 - 7.3 - 7.4 +- nightly services: - mysql From c9274a5f125dcd68e67a86509d346f1c1a4d6543 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:47:45 +0300 Subject: [PATCH 033/470] Deprectaed old PHP versions --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6cdf1d50421..e15ccb31b73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: php php: -- 7.1 -- 7.2 - 7.3 - 7.4 - nightly From c421cac868f2a9093312f3d422cdfc075a82434e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:48:36 +0300 Subject: [PATCH 034/470] Moved to travis-ci.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5a36bb5623..cc444edeede 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Utopia Abuse -[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.org/utopia-php/abuse) +[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/abuse) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) [![Discord](https://img.shields.io/discord/564160730845151244)](https://discord.gg/GSeTUeA) From 8e65890a6d7afa9f57992f1eca9fe64508f9822e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Oct 2020 09:51:42 +0300 Subject: [PATCH 035/470] Updated Readme and CI --- .travis.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e15ccb31b73..cf07431be4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ notifications: before_install: - mysql < data/schema.sql -before_script: composer install +before_script: composer install --ignore-platform-reqs script: - vendor/bin/phpunit --configuration phpunit.xml diff --git a/README.md b/README.md index cc444edeede..d58e53757f0 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/abuse) ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) -[![Discord](https://img.shields.io/discord/564160730845151244)](https://discord.gg/GSeTUeA) +[![Discord](https://img.shields.io/discord/564160730845151244)](https://appwrite.io/discord) -Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. +Utopia framework abuse library is simple and lite library for managing application usage limits. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free, and can be used as standalone with any other PHP project or framework. From 1cdb97938fda9006e7ca75900eec00cb5990307f Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Fri, 23 Oct 2020 17:00:21 +0300 Subject: [PATCH 036/470] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d58e53757f0..45cd413a38c 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ if(!$abuse->check()) { ## System Requirements -Utopia Framework requires PHP 7.0 or later. We recommend using the latest PHP version whenever possible. +Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. ## Authors @@ -82,4 +82,4 @@ Utopia Framework requires PHP 7.0 or later. We recommend using the latest PHP ve ## Copyright and license -The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) \ No newline at end of file +The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) From b66b508f616d4859bbb940dbe616f344454f7b07 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 16 Dec 2020 23:12:51 +0530 Subject: [PATCH 037/470] chore: testing delete logs older than X days --- README.md | 2 +- src/Abuse/Abuse.php | 12 ++++++++++++ src/Abuse/Adapter.php | 10 ++++++++++ src/Abuse/Adapters/TimeLimit.php | 19 +++++++++++++++++++ tests/Abuse/AbuseTest.php | 8 ++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 45cd413a38c..8599599034c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ composer require utopia-php/abuse **Time Limit Abuse** The time limit abuse allow each key (action) to be performed [X] times in given time frame. -This adapter uses a MySQL / MariaDB to store usage attempts. Before using it create the table schema as documented ate this repository (./data/schema.sql) +This adapter uses a MySQL / MariaDB to store usage attempts. Before using it create the table schema as documented in this repository (./data/schema.sql) ```php adapter->check(); } + + /** + * Delete all logs older than $seconds seconds + * + * @param int $seconds + * + * @return bool + */ + public function deleteLogsOlderThan(int $seconds): bool + { + return $this->adapter->deleteLogsOlderThan($seconds); + } } \ No newline at end of file diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 8df4af8a790..4de240f49eb 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -12,4 +12,14 @@ interface Adapter * @return bool */ public function check(); + + + /** + * Delete all logs older than $seconds seconds + * + * @param int $seconds + * + * @return bool + */ + public function deleteLogsOlderThan(int $seconds): bool; } \ No newline at end of file diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index ba466605e5c..d8806fdaf97 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -202,6 +202,25 @@ protected function hit(string $key, int $time): void $this->count++; } + /** + * Delete logs older than $seconds seconds + * + * @param int $seconds + * + * @return bool + */ + public function deleteLogsOlderThan(int $seconds):bool + { + $st = $this->getPDO()->prepare('DELETE + FROM `'.$this->getNamespace().'.abuse.abuse` + WHERE (UNIX_TIMESTAMP(NOW()) - CAST(`_time` AS SIGNED)) > :seconds'); + + $st->bindValue(':seconds', $seconds, PDO::PARAM_INT); + $st->execute(); + + return ('00000' == $st->errorCode()) ? true : false; + } + /** * Check * diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 9a9e63ec10c..8e8db7b8ce1 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -68,4 +68,12 @@ public function testIsValid() $this->assertEquals($this->abuse->check(), false); $this->assertEquals($this->abuse->check(), true); } + + public function testDeleteLogsOlderThan() { + sleep(3); + $status = $this->abuse->deleteLogsOlderThan(1); + $this->assertEquals($status, true); + + $this->assertEquals($this->abuse->check(), true); + } } From 6ba5dbda50dfe9d8b70b0fc99c1333ea6761a42f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 16 Dec 2020 23:17:37 +0530 Subject: [PATCH 038/470] chore: testing delete logs older than X days --- src/Abuse/Adapters/ReCaptcha.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 7321605d2b9..d02c067a1d3 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -79,4 +79,16 @@ public function check() return $result['success']; } + + /** + * Delete logs older than $seconds seconds + * + * @param int $seconds + * + * @return bool + */ + public function deleteLogsOlderThan(int $seconds):bool + { + return true; + } } \ No newline at end of file From 9c9a4e1c371d9f13b7259369afd0d58375b4c418 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 16 Dec 2020 23:18:41 +0530 Subject: [PATCH 039/470] chore: testing delete logs older than X days --- tests/Abuse/AbuseTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 8e8db7b8ce1..e11bd4ed815 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -70,10 +70,10 @@ public function testIsValid() } public function testDeleteLogsOlderThan() { - sleep(3); + sleep(5); $status = $this->abuse->deleteLogsOlderThan(1); $this->assertEquals($status, true); - $this->assertEquals($this->abuse->check(), true); + $this->assertEquals($this->abuse->check(), false); } } From 61afe66c01b99552f99e26b457922ee1c3731cc7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 16 Dec 2020 23:25:47 +0530 Subject: [PATCH 040/470] chore: testing delete logs older than X days --- src/Abuse/Adapters/TimeLimit.php | 2 +- tests/Abuse/AbuseTest.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index d8806fdaf97..70ac336df36 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -213,7 +213,7 @@ public function deleteLogsOlderThan(int $seconds):bool { $st = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.abuse.abuse` - WHERE (UNIX_TIMESTAMP(NOW()) - CAST(`_time` AS SIGNED)) > :seconds'); + WHERE (UNIX_TIMESTAMP(NOW()) - CAST(`_time` AS SIGNED)) > :seconds'); $st->bindValue(':seconds', $seconds, PDO::PARAM_INT); $st->execute(); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index e11bd4ed815..2950baf2659 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -70,7 +70,12 @@ public function testIsValid() } public function testDeleteLogsOlderThan() { - sleep(5); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), true); + + $status = $this->abuse->deleteLogsOlderThan(1); $this->assertEquals($status, true); From 0f5ed46d9d81741d86882cebe1c100ac1f379366 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 17:52:05 +0530 Subject: [PATCH 041/470] chore: testing delete logs older than X days --- src/Abuse/Abuse.php | 12 ++++++++++++ src/Abuse/Adapter.php | 9 +++++++++ src/Abuse/Adapters/TimeLimit.php | 18 ++++++++++++++++++ tests/Abuse/AbuseTest.php | 13 ++++++------- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index b8659d53de5..930e85a4f2e 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -29,6 +29,18 @@ public function check() return $this->adapter->check(); } + /** + * Get all abuse logs + * + * Returns all the abuse logs that are currently in the DB + * + * @return array + */ + public function getAllLogs(): array + { + return $this->adapter->getAllLogs(); + } + /** * Delete all logs older than $seconds seconds * diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 4de240f49eb..222506bc247 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -13,6 +13,15 @@ interface Adapter */ public function check(); + /** + * Get all abuse logs + * + * Returns all the abuse logs that are currently in the DB + * + * @return array + */ + public function getAllLogs(): array; + /** * Delete all logs older than $seconds seconds diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 70ac336df36..e9a46664a08 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -202,6 +202,24 @@ protected function hit(string $key, int $time): void $this->count++; } + /** + * Get all abuse logs + * + * Returns all the abuse logs that are currently in the DB + * + * @return array + */ + public function getAllLogs(): array { + + $st = $this->getPDO()->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`;'); + $st->execute(); + + $result = $st->fetchAll(); + + return $result; + } + + /** * Delete logs older than $seconds seconds * diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 2950baf2659..91c3fff6f70 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -70,15 +70,14 @@ public function testIsValid() } public function testDeleteLogsOlderThan() { - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), true); + $logs = $this->abuse->getAllLogs(); + var_dump($logs); + // $this->assertEquals(1, \count($logs)); - $status = $this->abuse->deleteLogsOlderThan(1); - $this->assertEquals($status, true); + // $status = $this->abuse->deleteLogsOlderThan(1); + // $this->assertEquals($status, true); - $this->assertEquals($this->abuse->check(), false); + // $this->assertEquals($this->abuse->check(), false); } } From 00fb037c8b83e51d9983c7c457f86b63acd1aa98 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 18:31:46 +0530 Subject: [PATCH 042/470] chore: testing delete logs older than X days --- src/Abuse/Adapters/ReCaptcha.php | 11 +++++++++++ tests/Abuse/AbuseTest.php | 11 ++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index d02c067a1d3..aa611b7eec8 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -91,4 +91,15 @@ public function deleteLogsOlderThan(int $seconds):bool { return true; } + + /** + * Get all logs + * + * Returns all the logs that are currently in the DB + * + * @return array + */ + public function getAllLogs(): array { + return array(); + } } \ No newline at end of file diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 91c3fff6f70..77dc9594e9d 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -72,12 +72,13 @@ public function testIsValid() public function testDeleteLogsOlderThan() { $logs = $this->abuse->getAllLogs(); - var_dump($logs); - // $this->assertEquals(1, \count($logs)); + $this->assertEquals(1, \count($logs)); - // $status = $this->abuse->deleteLogsOlderThan(1); - // $this->assertEquals($status, true); + sleep(5); + $status = $this->abuse->deleteLogsOlderThan(1); + $this->assertEquals($status, true); - // $this->assertEquals($this->abuse->check(), false); + $logs = $this->abuse->getAllLogs(); + $this->assertEquals(0, \count($logs)); } } From cf4f9aec1f0792d1c62ee4db44c9a0a64fa11e06 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 18:46:02 +0530 Subject: [PATCH 043/470] chore: testing delete logs older than X days --- src/Abuse/Adapters/ReCaptcha.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index aa611b7eec8..4ccbab693df 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -99,7 +99,8 @@ public function deleteLogsOlderThan(int $seconds):bool * * @return array */ - public function getAllLogs(): array { + public function getAllLogs(): array + { return array(); } } \ No newline at end of file From 06dc64fc6449bbc1075d81af282ee82aa1dc700b Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 18:46:34 +0530 Subject: [PATCH 044/470] chore: testing delete logs older than X days --- tests/Abuse/AbuseTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 77dc9594e9d..9100b3a8d11 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -71,13 +71,16 @@ public function testIsValid() public function testDeleteLogsOlderThan() { + // Check that there is only one log $logs = $this->abuse->getAllLogs(); $this->assertEquals(1, \count($logs)); sleep(5); + // Delete the log $status = $this->abuse->deleteLogsOlderThan(1); $this->assertEquals($status, true); + // Check that there are no logs in the DB $logs = $this->abuse->getAllLogs(); $this->assertEquals(0, \count($logs)); } From 0aaba0910b2246a1f8108cf1da99b10c33b904b2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 20:45:07 +0530 Subject: [PATCH 045/470] chore: add params for get logs --- src/Abuse/Abuse.php | 11 +++++++---- src/Abuse/Adapter.php | 9 ++++++--- src/Abuse/Adapters/ReCaptcha.php | 15 ++++++++++----- src/Abuse/Adapters/TimeLimit.php | 15 +++++++++++---- tests/Abuse/AbuseTest.php | 4 ++-- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 930e85a4f2e..807ed2ad9b9 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -30,15 +30,18 @@ public function check() } /** - * Get all abuse logs + * Get abuse logs * - * Returns all the abuse logs that are currently in the DB + * Returns logs with an offset and limit * + * @param $offset + * @param $limit + * * @return array */ - public function getAllLogs(): array + public function getLogs(int $offset, int $limit): array { - return $this->adapter->getAllLogs(); + return $this->adapter->getLogs($offset, $limit); } /** diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 222506bc247..75ee1bd0460 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -14,13 +14,16 @@ interface Adapter public function check(); /** - * Get all abuse logs + * Get abuse logs * - * Returns all the abuse logs that are currently in the DB + * Returns logs with an offset and limit * + * @param $offset + * @param $limit + * * @return array */ - public function getAllLogs(): array; + public function getLogs(int $offset, int $limit): array; /** diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 4ccbab693df..3994ab67cbe 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -2,6 +2,7 @@ namespace Utopia\Abuse\Adapters; +use Exception; use Utopia\Abuse\Adapter; class ReCaptcha implements Adapter @@ -85,22 +86,26 @@ public function check() * * @param int $seconds * + * @throws Exception * @return bool */ public function deleteLogsOlderThan(int $seconds):bool { - return true; + throw new Exception('Method not supported'); } /** - * Get all logs + * Get abuse logs * - * Returns all the logs that are currently in the DB + * Returns logs with an offset and limit * + * @param $offset + * @param $limit + * * @return array */ - public function getAllLogs(): array + public function getLogs(int $offset, int $limit): array { - return array(); + throw new Exception('Method not supported'); } } \ No newline at end of file diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e9a46664a08..092f8edd17b 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -203,15 +203,22 @@ protected function hit(string $key, int $time): void } /** - * Get all abuse logs + * Get abuse logs * - * Returns all the abuse logs that are currently in the DB + * Returns logs with an offset and limit * + * @param $offset + * @param $limit + * * @return array */ - public function getAllLogs(): array { + public function getLogs(int $offset, int $limit): array { - $st = $this->getPDO()->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`;'); + $st = $this->getPDO()->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`; + LIMIT :offset, :limit + '); + $st->bindValue(':offset', $offset, PDO::PARAM_INT); + $st->bindValue(':limit', $limit, PDO::PARAM_INT); $st->execute(); $result = $st->fetchAll(); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 9100b3a8d11..2d29f7b03d6 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -72,7 +72,7 @@ public function testIsValid() public function testDeleteLogsOlderThan() { // Check that there is only one log - $logs = $this->abuse->getAllLogs(); + $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(1, \count($logs)); sleep(5); @@ -81,7 +81,7 @@ public function testDeleteLogsOlderThan() { $this->assertEquals($status, true); // Check that there are no logs in the DB - $logs = $this->abuse->getAllLogs(); + $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(0, \count($logs)); } } From 79675947e7abf53abd527c6a3f29bf9fcaed22a2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 20:54:59 +0530 Subject: [PATCH 046/470] chore: review comments --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 092f8edd17b..640c94e53c9 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -220,8 +220,8 @@ public function getLogs(int $offset, int $limit): array { $st->bindValue(':offset', $offset, PDO::PARAM_INT); $st->bindValue(':limit', $limit, PDO::PARAM_INT); $st->execute(); - - $result = $st->fetchAll(); + + $result = $st->fetchAll(); return $result; } From 4811074ae0ef1f91fb4349d3d2a4e3c4750afb85 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 17 Dec 2020 20:59:42 +0530 Subject: [PATCH 047/470] chore: changed function name --- src/Abuse/Abuse.php | 4 ++-- src/Abuse/Adapter.php | 2 +- src/Abuse/Adapters/ReCaptcha.php | 2 +- src/Abuse/Adapters/TimeLimit.php | 2 +- tests/Abuse/AbuseTest.php | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 807ed2ad9b9..6243863e450 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -51,8 +51,8 @@ public function getLogs(int $offset, int $limit): array * * @return bool */ - public function deleteLogsOlderThan(int $seconds): bool + public function cleanup(int $seconds): bool { - return $this->adapter->deleteLogsOlderThan($seconds); + return $this->adapter->cleanup($seconds); } } \ No newline at end of file diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 75ee1bd0460..f5e55311357 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -33,5 +33,5 @@ public function getLogs(int $offset, int $limit): array; * * @return bool */ - public function deleteLogsOlderThan(int $seconds): bool; + public function cleanup(int $seconds): bool; } \ No newline at end of file diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 3994ab67cbe..6fda1a1b578 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -89,7 +89,7 @@ public function check() * @throws Exception * @return bool */ - public function deleteLogsOlderThan(int $seconds):bool + public function cleanup(int $seconds):bool { throw new Exception('Method not supported'); } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 640c94e53c9..45444f4f7de 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -234,7 +234,7 @@ public function getLogs(int $offset, int $limit): array { * * @return bool */ - public function deleteLogsOlderThan(int $seconds):bool + public function cleanup(int $seconds):bool { $st = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.abuse.abuse` diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 2d29f7b03d6..0d6464b776c 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -69,7 +69,7 @@ public function testIsValid() $this->assertEquals($this->abuse->check(), true); } - public function testDeleteLogsOlderThan() { + public function testCleanup() { // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); @@ -77,7 +77,7 @@ public function testDeleteLogsOlderThan() { sleep(5); // Delete the log - $status = $this->abuse->deleteLogsOlderThan(1); + $status = $this->abuse->cleanup(1); $this->assertEquals($status, true); // Check that there are no logs in the DB From 246c1a1745abd125f0005bc21ab1448e7edf9f93 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 21 Dec 2020 22:44:46 +0530 Subject: [PATCH 048/470] patch: fix query, modify tests --- src/Abuse/Abuse.php | 8 ++++---- src/Abuse/Adapter.php | 6 +++--- src/Abuse/Adapters/ReCaptcha.php | 6 +++--- src/Abuse/Adapters/TimeLimit.php | 14 +++++++------- tests/Abuse/AbuseTest.php | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 6243863e450..ea263e7e699 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -45,14 +45,14 @@ public function getLogs(int $offset, int $limit): array } /** - * Delete all logs older than $seconds seconds + * Delete all logs older than $timestamp seconds * - * @param int $seconds + * @param int $timestamp * * @return bool */ - public function cleanup(int $seconds): bool + public function cleanup(int $timestamp): bool { - return $this->adapter->cleanup($seconds); + return $this->adapter->cleanup($timestamp); } } \ No newline at end of file diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index f5e55311357..3a38289cb7b 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -27,11 +27,11 @@ public function getLogs(int $offset, int $limit): array; /** - * Delete all logs older than $seconds seconds + * Delete all logs older than $timestamp seconds * - * @param int $seconds + * @param int $timestamp * * @return bool */ - public function cleanup(int $seconds): bool; + public function cleanup(int $timestamp): bool; } \ No newline at end of file diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 6fda1a1b578..30eb252001d 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -82,14 +82,14 @@ public function check() } /** - * Delete logs older than $seconds seconds + * Delete logs older than $timestamp seconds * - * @param int $seconds + * @param int $timestamp * * @throws Exception * @return bool */ - public function cleanup(int $seconds):bool + public function cleanup(int $timestamp):bool { throw new Exception('Method not supported'); } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 45444f4f7de..b1fe5c1d20f 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -228,22 +228,22 @@ public function getLogs(int $offset, int $limit): array { /** - * Delete logs older than $seconds seconds + * Delete logs older than $timestamp seconds * - * @param int $seconds + * @param int $timestamp * * @return bool */ - public function cleanup(int $seconds):bool + public function cleanup(int $timestamp):bool { $st = $this->getPDO()->prepare('DELETE FROM `'.$this->getNamespace().'.abuse.abuse` - WHERE (UNIX_TIMESTAMP(NOW()) - CAST(`_time` AS SIGNED)) > :seconds'); + WHERE `_time` < :timestamp'); - $st->bindValue(':seconds', $seconds, PDO::PARAM_INT); - $st->execute(); + $st->bindValue(':timestamp', $timestamp, PDO::PARAM_INT); + $result = $st->execute(); - return ('00000' == $st->errorCode()) ? true : false; + return $result == true; } /** diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 0d6464b776c..308bb4131f3 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -77,7 +77,7 @@ public function testCleanup() { sleep(5); // Delete the log - $status = $this->abuse->cleanup(1); + $status = $this->abuse->cleanup(time()-1); $this->assertEquals($status, true); // Check that there are no logs in the DB From b89e1d0cfed8c81958b3782cba2994a0b7fceb9b Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Tue, 12 Jan 2021 01:39:56 +0200 Subject: [PATCH 049/470] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cf07431be4b..372934689f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: php php: - 7.3 - 7.4 +- 8.0 - nightly services: From ed3fe031d7909e7c39c78e699abb7a12ab11dc1c Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Tue, 12 Jan 2021 01:53:20 +0200 Subject: [PATCH 050/470] Update .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 372934689f5..fdab5c37374 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: -- 7.3 - 7.4 - 8.0 - nightly From 203f2ac99ed31a0b9081450d1cb58ad3add7f1ef Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Tue, 12 Jan 2021 01:53:39 +0200 Subject: [PATCH 051/470] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8599599034c..59cdd93099b 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ if(!$abuse->check()) { ## System Requirements -Utopia Framework requires PHP 7.3 or later. We recommend using the latest PHP version whenever possible. +Utopia Framework requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. ## Authors From b2772e797e52d23900616809859e483f5d0639a7 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Tue, 12 Jan 2021 01:54:00 +0200 Subject: [PATCH 052/470] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cad6f336716..8f35e3797a2 100755 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, "require": { - "php": ">=7.1", + "php": ">=7.4", "ext-pdo": "*" }, "require-dev": { From f4e25c88c724178eda6c89a3882385c8d258bbb2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 17 Mar 2021 11:17:49 -0400 Subject: [PATCH 053/470] Allow custom score thresholds for reCAPTCHA --- src/Abuse/Adapters/ReCaptcha.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 30eb252001d..94416b6e8e8 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -53,8 +53,12 @@ public function __construct($secret, $response, $remoteIP) * Check * * Check if user is human or not + * + * @param float $score + * + * @return bool */ - public function check() + public function check($score):bool { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array( @@ -77,8 +81,11 @@ public function check() //close connection \curl_close($ch); - - return $result['success']; + if ($result['success'] && $result['score'] < $score ) { + return true; + } else { + return false; + } } /** @@ -108,4 +115,4 @@ public function getLogs(int $offset, int $limit): array { throw new Exception('Method not supported'); } -} \ No newline at end of file +} From 68e18942fa5b79dbce33a4c02fa2d6543ec338e0 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 17 Mar 2021 15:13:39 -0400 Subject: [PATCH 054/470] Make $score an optional --- src/Abuse/Adapters/ReCaptcha.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 94416b6e8e8..d1ea6b8e4f7 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -58,7 +58,7 @@ public function __construct($secret, $response, $remoteIP) * * @return bool */ - public function check($score):bool + public function check($score = 0.5):bool { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array( From 66eb8943390250516417df0b24ec6e917d42884a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 17 Mar 2021 15:22:42 -0400 Subject: [PATCH 055/470] Update docblock --- src/Abuse/Adapters/ReCaptcha.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index d1ea6b8e4f7..a9a9e9db5a5 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -52,10 +52,9 @@ public function __construct($secret, $response, $remoteIP) /** * Check * - * Check if user is human or not + * Check if user is human or not, compared to score * * @param float $score - * * @return bool */ public function check($score = 0.5):bool From 35fcfef44b7ecd6f6a7c64682a216aad92358b29 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 15:01:06 +0545 Subject: [PATCH 056/470] added dependency --- composer.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8f35e3797a2..da935718e85 100755 --- a/composer.json +++ b/composer.json @@ -16,10 +16,17 @@ }, "require": { "php": ">=7.4", - "ext-pdo": "*" + "ext-pdo": "*", + "utopia-php/database": "dev-feat-new-doc-methods" }, "require-dev": { "phpunit/phpunit": "^9.4", "vimeo/psalm": "4.0.1" - } + }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/utopia-php/database" + } + ] } From d40ab2b5aad1f1e9fbfedd5178c0bbd6a6563eac Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:01:53 +0545 Subject: [PATCH 057/470] integrating utopia-db --- src/Abuse/Adapters/TimeLimit.php | 195 +++++++++++++++---------------- tests/Abuse/AbuseTest.php | 54 ++++----- 2 files changed, 120 insertions(+), 129 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b1fe5c1d20f..e06af216583 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -4,14 +4,21 @@ use Exception; use PDO; +use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; +use Utopia\Database\Database; +use Utopia\Database\Document; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; class TimeLimit implements Adapter { + const COLLECTION = "abuse"; + /** - * @var callable + * @var Database */ - protected $connection; + protected $db; /** * @var string @@ -44,54 +51,25 @@ class TimeLimit implements Adapter * @param int $limit * @param callable $connection */ - public function __construct(string $key, int $limit, int $time, callable $connection) - { - $this->key = $key; - $this->time = (int)\date('U', (int)(\floor(\time() / $time)) * $time); - $this->limit = $limit; - $this->connection = $connection; - } - - /** - * @var string - */ - protected $namespace = ''; - - /** - * Set Namespace - * - * Set namespace to divide different scope of data sets - * - * @param string $namespace - * @throws Exception - * @return $this - */ - public function setNamespace(string $namespace): self + public function __construct(string $key, int $limit, int $time, Database $db) { - if(empty($namespace)) { - throw new Exception('Missing namespace'); - } - - $this->namespace = $namespace; - - return $this; + $this->key = $key; + $this->time = (int) \date('U', (int) (\floor(\time() / $time)) * $time); + $this->limit = $limit; + $this->db = $db; } - /** - * Get Namespace - * - * Get namespace of current set scope - * - * @throws Exception - * @return string - */ - public function getNamespace(): string + public function setup() { - if(empty($this->namespace)) { - throw new Exception('Missing namespace'); + if (!$this->db->exists()) { + $this->db->create(); + $this->db->createCollection(TimeLimit::COLLECTION); + $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, 45, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_STRING, 45, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); + $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); } - - return $this->namespace; } /** @@ -101,7 +79,7 @@ public function getNamespace(): string * * @param string $key * @param string $value - * + * * @return $this */ public function setParam(string $key, string $value): self @@ -130,7 +108,7 @@ protected function getParams(): array */ protected function parseKey(): string { - foreach($this->getParams() as $key => $value) { + foreach ($this->getParams() as $key => $value) { $this->key = \str_replace($key, $value, $this->key); } @@ -149,28 +127,28 @@ protected function parseKey(): string */ protected function count(string $key, int $time): int { - if(0 == $this->limit) { // No limit no point for counting + if (0 == $this->limit) { // No limit no point for counting return 0; } - if(!\is_null($this->count)) { // Get fetched result + if (!\is_null($this->count)) { // Get fetched result return $this->count; } - - $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` - WHERE _key = :key AND _time = :time - LIMIT 1; - '); - - $st->bindValue(':key', $key, PDO::PARAM_STR); - $st->bindValue(':time', $time, PDO::PARAM_STR); - - $st->execute(); - $result = $st->fetch(); - $result = (\is_array($result) && isset($result['_count'])) ? $result['_count'] : 0; + Authorization::disable(); + $result = $this->db->find(TimeLimit::COLLECTION, [ + new Query('_key', Query::TYPE_EQUAL, [$key]), + new Query('_time', Query::TYPE_EQUAL, [$time]), + ], 1); + Authorization::reset(); + + if (count($result) == 1) { + $result = (\is_array($result[0]) && isset($result[0]['_count'])) ? $result[0]['_count'] : 0; + } else { + $result = 0; + } - $this->count = (int)$result; + $this->count = (int) $result; return $this->count; } @@ -178,26 +156,41 @@ protected function count(string $key, int $time): int /** * @param string $key * @param int $time seconds - * + * * @throws Exception - * + * * @return void */ protected function hit(string $key, int $time): void { - if(0 == $this->limit) { // No limit no point for counting + if (0 == $this->limit) { // No limit no point for counting return; } - $st = $this->getPDO()->prepare('INSERT INTO `' . $this->getNamespace() . '.abuse.abuse` - SET _key = :key, _time = :time, _count = 1 - ON DUPLICATE KEY UPDATE _count = _count + 1; - '); - - $st->bindValue(':key', $key, PDO::PARAM_STR); - $st->bindValue(':time', $time, PDO::PARAM_STR); - - $st->execute(); + $existing = $this->db->find(TimeLimit::COLLECTION, [ + new Query('_key', Query::TYPE_EQUAL, [$key]), + new Query('_time', Query::TYPE_EQUAL, [$time]), + ], 1); + + $data = [ + '$read' => [], + '$write' => [], + '_key' => $key, + '_time' => $time, + '_count' => 1, + ]; + + Authorization::disable(); + if (count($existing) == 1) { + //update + $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]['$id'], new Document(array_merge($data, [ + '_count' => $existing[0]['_count'] + 1, + ]))); + } else { + //create + $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + } + Authorization::reset(); $this->count++; } @@ -207,45 +200,41 @@ protected function hit(string $key, int $time): void * * Returns logs with an offset and limit * - * @param $offset + * @param $offset * @param $limit - * + * * @return array */ - public function getLogs(int $offset, int $limit): array { - - $st = $this->getPDO()->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`; - LIMIT :offset, :limit - '); - $st->bindValue(':offset', $offset, PDO::PARAM_INT); - $st->bindValue(':limit', $limit, PDO::PARAM_INT); - $st->execute(); - - $result = $st->fetchAll(); - + public function getLogs(int $offset, int $limit): array + { + Authorization::disable(); + $result = $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset); + Authorization::reset(); return $result; } - /** * Delete logs older than $timestamp seconds - * - * @param int $timestamp - * - * @return bool + * + * @param int $timestamp + * + * @return bool */ - public function cleanup(int $timestamp):bool + public function cleanup(int $timestamp): bool { - $st = $this->getPDO()->prepare('DELETE - FROM `'.$this->getNamespace().'.abuse.abuse` - WHERE `_time` < :timestamp'); + Authorization::disable(); + $documents = $this->db->find(TimeLimit::COLLECTION, [ + new Query('_time', Query::TYPE_LESSER, [$timestamp]), + ]); - $st->bindValue(':timestamp', $timestamp, PDO::PARAM_INT); - $result = $st->execute(); + foreach ($documents as $document) { + $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); + } + Authorization::reset(); - return $result == true; + return true; } - + /** * Check * @@ -256,13 +245,13 @@ public function cleanup(int $timestamp):bool */ public function check(): bool { - if(0 == $this->limit) { + if (0 == $this->limit) { return false; } $key = $this->parseKey(); - if($this->limit > $this->count($key, $this->time)) { + if ($this->limit > $this->count($key, $this->time)) { $this->hit($key, $this->time); return false; } @@ -276,7 +265,7 @@ public function check(): bool * Returns the number of current remaining counts * * @throws Exception - * + * * @return int */ public function remaining(): int diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 308bb4131f3..1057cfdac16 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -14,10 +14,13 @@ namespace Utopia\Tests; use PDO; +use PHPUnit\Framework\TestCase; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; - -use PHPUnit\Framework\TestCase; +use Utopia\Cache\Adapter\None as NoCache; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MySQL; +use Utopia\Database\Database; class AbuseTest extends TestCase { @@ -26,36 +29,34 @@ class AbuseTest extends TestCase */ protected $abuse = null; - public function setUp():void + public function setUp(): void { // Limit login attempts to 3 time in 5 minutes time frame - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), function () { - $dbHost = '127.0.0.1'; - $dbUser = 'travis'; - $dbPass = ''; - $dbName = 'abuse'; + $dbHost = '127.0.0.1'; + $dbUser = 'travis'; + $dbPass = ''; + $dbName = 'abuse'; - $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - PDO::ATTR_TIMEOUT => 5 // Seconds - )); + $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + PDO::ATTR_TIMEOUT => 5, // Seconds + )); - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - return $pdo; - }); + $db = new Database(new MySQL($pdo), new Cache(new NoCache())); - $adapter - ->setNamespace('namespace') // DB table namespace - ->setParam('{{ip}}', '127.0.0.1') - ; + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); + $adapter->setup(); + + $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); } - public function tearDown():void + public function tearDown(): void { $this->abuse = null; } @@ -69,15 +70,16 @@ public function testIsValid() $this->assertEquals($this->abuse->check(), true); } - public function testCleanup() { + public function testCleanup() + { // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(1, \count($logs)); - + sleep(5); - // Delete the log - $status = $this->abuse->cleanup(time()-1); + // Delete the log + $status = $this->abuse->cleanup(time() - 1); $this->assertEquals($status, true); // Check that there are no logs in the DB From 5f9e9b4fb54999cc965aa41896ab03b848453dde Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:11:27 +0545 Subject: [PATCH 058/470] fix test issues --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- tests/Abuse/AbuseTest.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e06af216583..6cc1a7496e3 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -59,7 +59,7 @@ public function __construct(string $key, int $limit, int $time, Database $db) $this->db = $db; } - public function setup() + public function setup() : void { if (!$this->db->exists()) { $this->db->create(); @@ -143,7 +143,7 @@ protected function count(string $key, int $time): int Authorization::reset(); if (count($result) == 1) { - $result = (\is_array($result[0]) && isset($result[0]['_count'])) ? $result[0]['_count'] : 0; + $result = $result[0]->getAttribute('_count',0); } else { $result = 0; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 1057cfdac16..11cc322925d 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -47,7 +47,8 @@ public function setUp(): void $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); - + $db->setNamespace('namespace'); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); $adapter->setup(); From 4aaa05c3e95c1099d8c6738dc8bd6f4661982e9a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:18:24 +0545 Subject: [PATCH 059/470] fix more issues --- src/Abuse/Adapters/TimeLimit.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 6cc1a7496e3..ae8da67baac 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -64,8 +64,8 @@ public function setup() : void if (!$this->db->exists()) { $this->db->create(); $this->db->createCollection(TimeLimit::COLLECTION); - $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, 45, true); - $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_STRING, 45, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, Database::LENGTH_KEY, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); @@ -184,7 +184,7 @@ protected function hit(string $key, int $time): void if (count($existing) == 1) { //update $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]['$id'], new Document(array_merge($data, [ - '_count' => $existing[0]['_count'] + 1, + '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); } else { //create From 3232862b1f8af964a5dec1b8417443d0366af7f7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:22:48 +0545 Subject: [PATCH 060/470] fix auth disable in proper place --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index ae8da67baac..98ec5feb8a8 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -167,6 +167,7 @@ protected function hit(string $key, int $time): void return; } + Authorization::disable(); $existing = $this->db->find(TimeLimit::COLLECTION, [ new Query('_key', Query::TYPE_EQUAL, [$key]), new Query('_time', Query::TYPE_EQUAL, [$time]), @@ -180,10 +181,9 @@ protected function hit(string $key, int $time): void '_count' => 1, ]; - Authorization::disable(); if (count($existing) == 1) { //update - $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]['$id'], new Document(array_merge($data, [ + $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getAttribute('id'), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); } else { From 22b248d37b1259b65a70fe73c47365a3cd5a510a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:25:14 +0545 Subject: [PATCH 061/470] fix attribute name --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 98ec5feb8a8..4ce1e4f46c7 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -183,7 +183,7 @@ protected function hit(string $key, int $time): void if (count($existing) == 1) { //update - $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getAttribute('id'), new Document(array_merge($data, [ + $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getAttribute('$id'), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); } else { From b93eab2986cb48f182896183201acd12b11b15b2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:27:59 +0545 Subject: [PATCH 062/470] fix get id --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 4ce1e4f46c7..494db47103f 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -183,7 +183,7 @@ protected function hit(string $key, int $time): void if (count($existing) == 1) { //update - $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getAttribute('$id'), new Document(array_merge($data, [ + $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); } else { From 0518e75cac5c62dc838f9de4ca1bef7b74daf927 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:31:20 +0545 Subject: [PATCH 063/470] remove getpdo --- src/Abuse/Adapters/TimeLimit.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 494db47103f..067f89a0042 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -297,9 +297,4 @@ public function time(): int { return $this->time; } - - protected function getPDO(): PDO - { - return \call_user_func($this->connection); - } } From 907f36118e2b68042eec7671a5700691d9984f78 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:32:31 +0545 Subject: [PATCH 064/470] var dump existing --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 067f89a0042..98cc83a32e9 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -183,6 +183,7 @@ protected function hit(string $key, int $time): void if (count($existing) == 1) { //update + var_dump($existing); $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); From e1754cb593272c2ab484f0561fa1d60cb2628c6e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:36:37 +0545 Subject: [PATCH 065/470] more dumps --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 98cc83a32e9..1a51ac9044c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -184,6 +184,7 @@ protected function hit(string $key, int $time): void if (count($existing) == 1) { //update var_dump($existing); + var_dump($existing[0]->getId()); $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, ]))); From 6ffb076536b7f0408d06c71b7744e4f8c5c1ac6a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:41:36 +0545 Subject: [PATCH 066/470] add id to the data array as well --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 1a51ac9044c..86ff347603a 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -187,6 +187,7 @@ protected function hit(string $key, int $time): void var_dump($existing[0]->getId()); $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, + '$id' => $existing[0]->getId(), ]))); } else { //create From 4797c20e20593c5be65034be6fd0568d5d0c73bb Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 17 May 2021 16:44:57 +0545 Subject: [PATCH 067/470] pass $collection and remove var dumps --- src/Abuse/Adapters/TimeLimit.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 86ff347603a..074d1506990 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -179,12 +179,11 @@ protected function hit(string $key, int $time): void '_key' => $key, '_time' => $time, '_count' => 1, + '$collection' => TimeLimit::COLLECTION, ]; if (count($existing) == 1) { //update - var_dump($existing); - var_dump($existing[0]->getId()); $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, '$id' => $existing[0]->getId(), From b5aed0b31c36297d5316c8bb0a93af5e14fd3086 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 18 May 2021 11:12:11 +0545 Subject: [PATCH 068/470] remove stale schema --- .travis.yml | 3 --- data/schema.sql | 13 ------------- 2 files changed, 16 deletions(-) delete mode 100644 data/schema.sql diff --git a/.travis.yml b/.travis.yml index fdab5c37374..34f1eb261ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,6 @@ notifications: email: - team@appwrite.io -before_install: -- mysql < data/schema.sql - before_script: composer install --ignore-platform-reqs script: diff --git a/data/schema.sql b/data/schema.sql deleted file mode 100644 index 4adaed098ca..00000000000 --- a/data/schema.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE DATABASE abuse; - -USE abuse; - -CREATE TABLE `namespace.abuse.abuse` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `_key` varchar(255) NOT NULL, - `_time` varchar(45) NOT NULL, - `_count` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - UNIQUE KEY `unique1` (`_key`,`_time`), - KEY `index1` (`_key`,`_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file From 6b6dd1b1f912489a6c61220110a95968f8cb0f76 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 18 May 2021 11:12:37 +0545 Subject: [PATCH 069/470] update utopia db version --- composer.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index da935718e85..84f5681435c 100755 --- a/composer.json +++ b/composer.json @@ -17,16 +17,10 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "dev-feat-new-doc-methods" + "utopia-php/database": "0.1.*" }, "require-dev": { "phpunit/phpunit": "^9.4", "vimeo/psalm": "4.0.1" - }, - "repositories": [ - { - "type": "git", - "url": "https://github.com/utopia-php/database" - } - ] + } } From 2ae3ec680691161247b3ecedf324a9ef53837e71 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 18 May 2021 12:39:59 +0545 Subject: [PATCH 070/470] remove non existing db --- tests/Abuse/AbuseTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 11cc322925d..e27251b8bcb 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -35,9 +35,8 @@ public function setUp(): void $dbHost = '127.0.0.1'; $dbUser = 'travis'; $dbPass = ''; - $dbName = 'abuse'; - $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( + $pdo = new PDO("mysql:host={$dbHost};", $dbUser, $dbPass, array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', PDO::ATTR_TIMEOUT => 5, // Seconds )); From 2a81b3a5b296547bd4748b420f567deb6f979b0e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 18 May 2021 13:09:36 +0545 Subject: [PATCH 071/470] update readme --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59cdd93099b..43c009704ba 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,29 @@ require_once __DIR__ . '/../../vendor/autoload.php'; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Cache\Adapter\None as NoCache; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MySQL; +use Utopia\Database\Database; + +$dbHost = '127.0.0.1'; +$dbUser = 'travis'; +$dbPass = ''; + +$pdo = new PDO("mysql:host={$dbHost};", $dbUser, $dbPass, array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + PDO::ATTR_TIMEOUT => 5, // Seconds +)); + +// Connection settings +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays +$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$db = new Database(new MySQL($pdo), new Cache(new NoCache())); +$db->setNamespace('namespace'); // Limit login attempts to 10 time in 5 minutes time frame -$adapter = new TimeLimit('login-attempt-from-{{ip}}', 10, (60 * 5), function () {/* init and return PDO connection... */}); +$adapter = new TimeLimit('login-attempt-from-{{ip}}', 10, (60 * 5), $db); $adapter ->setNamespace('namespace') // DB table namespace From cfbc1a23ae55235c2c8b1c790932bf18e6edf929 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 18 May 2021 13:10:51 +0545 Subject: [PATCH 072/470] fix readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 43c009704ba..b402a60dc05 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,8 @@ $db->setNamespace('namespace'); // Limit login attempts to 10 time in 5 minutes time frame $adapter = new TimeLimit('login-attempt-from-{{ip}}', 10, (60 * 5), $db); -$adapter - ->setNamespace('namespace') // DB table namespace - ->setParam('{{ip}}', '127.0.0.1') +$adapter->setup(); //setup database as required +$adapter->setParam('{{ip}}', '127.0.0.1') ; $abuse = new Abuse($adapter); From 525cdf6674e11a7229cba31941915ac50a3e575a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 4 Jun 2021 14:25:29 +0545 Subject: [PATCH 073/470] update db version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 84f5681435c..7184bce4fd8 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.1.*" + "utopia-php/database": "0.2.*" }, "require-dev": { "phpunit/phpunit": "^9.4", From 2d1de283fdb78818d8838fe2ac0284900e787825 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 5 Jun 2021 16:49:59 +0300 Subject: [PATCH 074/470] Fixed score check --- src/Abuse/Adapters/ReCaptcha.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index a9a9e9db5a5..b9887d92496 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -80,7 +80,7 @@ public function check($score = 0.5):bool //close connection \curl_close($ch); - if ($result['success'] && $result['score'] < $score ) { + if ($result['success'] && $result['score'] >= $score ) { return true; } else { return false; From 4f3349b3c1c85353708d13f7eb3c34c0762d4828 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 9 Jun 2021 14:56:16 +0545 Subject: [PATCH 075/470] fix setup script --- src/Abuse/Adapters/TimeLimit.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 074d1506990..dd86a252427 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -63,13 +63,13 @@ public function setup() : void { if (!$this->db->exists()) { $this->db->create(); - $this->db->createCollection(TimeLimit::COLLECTION); - $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); - $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); - $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); } + $this->db->createCollection(TimeLimit::COLLECTION); + $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, Database::LENGTH_KEY, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); + $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); + $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); } /** From b4fafd8e983da28a01858c69650a0a54275910db Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 11:34:46 +0545 Subject: [PATCH 076/470] Apply suggestions from code review Co-authored-by: kodumbeats --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index dd86a252427..b53d05b901f 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -142,7 +142,7 @@ protected function count(string $key, int $time): int ], 1); Authorization::reset(); - if (count($result) == 1) { + if (\count($result) === 1) { $result = $result[0]->getAttribute('_count',0); } else { $result = 0; @@ -182,7 +182,7 @@ protected function hit(string $key, int $time): void '$collection' => TimeLimit::COLLECTION, ]; - if (count($existing) == 1) { + if (\count($existing) === 1) { //update $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ '_count' => $existing[0]->getAttribute('_count',0) + 1, From 012419cc8d65e992df27b67ecd6544a43da55778 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 13:48:23 +0545 Subject: [PATCH 077/470] more index --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b53d05b901f..784db75e00e 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -70,6 +70,7 @@ public function setup() : void $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'index2', Database::INDEX_KEY, ['_time']); } /** From df15d7e42f2477d2e3753e4426ad5cec4394e2dc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 13:51:06 +0545 Subject: [PATCH 078/470] fix setup issue --- src/Abuse/Adapters/TimeLimit.php | 5 ++--- tests/Abuse/AbuseTest.php | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 784db75e00e..68f89684dc1 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -2,14 +2,13 @@ namespace Utopia\Abuse\Adapters; -use Exception; use PDO; -use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Exception; class TimeLimit implements Adapter { @@ -62,7 +61,7 @@ public function __construct(string $key, int $limit, int $time, Database $db) public function setup() : void { if (!$this->db->exists()) { - $this->db->create(); + throw new Exception("You need to create database before running timelimit setup"); } $this->db->createCollection(TimeLimit::COLLECTION); $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, Database::LENGTH_KEY, true); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index e27251b8bcb..21e759c174e 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -28,6 +28,7 @@ class AbuseTest extends TestCase * @var Abuse */ protected $abuse = null; + protected $initialized = false; public function setUp(): void { @@ -49,7 +50,10 @@ public function setUp(): void $db->setNamespace('namespace'); $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - $adapter->setup(); + if(! $this->initialized) { + $db->create(); + $adapter->setup(); + } $adapter->setParam('{{ip}}', '127.0.0.1'); From 1338a402e4a87053e3f12ae86f9ae91d3ad2f5b5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 13:52:45 +0545 Subject: [PATCH 079/470] improve cleanup method --- src/Abuse/Adapters/TimeLimit.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 68f89684dc1..d8fe4302298 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -225,13 +225,15 @@ public function getLogs(int $offset, int $limit): array public function cleanup(int $timestamp): bool { Authorization::disable(); - $documents = $this->db->find(TimeLimit::COLLECTION, [ - new Query('_time', Query::TYPE_LESSER, [$timestamp]), - ]); - - foreach ($documents as $document) { - $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); - } + do { + $documents = $this->db->find(TimeLimit::COLLECTION, [ + new Query('_time', Query::TYPE_LESSER, [$timestamp]), + ]); + + foreach ($documents as $document) { + $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); + } + } while(\count($documents) > 0); Authorization::reset(); return true; From 66427c55990238f82e940c4ebfe876f73b2a644a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 15:38:24 +0545 Subject: [PATCH 080/470] fix test --- tests/Abuse/AbuseTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 21e759c174e..bfd693d4f57 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -50,9 +50,10 @@ public function setUp(): void $db->setNamespace('namespace'); $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - if(! $this->initialized) { + if(!$this->initialized) { $db->create(); $adapter->setup(); + $this->initialized = true; } $adapter->setParam('{{ip}}', '127.0.0.1'); From 1495095e6f572f371f22c8f0106632ba2cd55ee8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 16:24:34 +0545 Subject: [PATCH 081/470] fix setup and db fields --- src/Abuse/Adapters/TimeLimit.php | 24 ++++++++++++------------ tests/Abuse/AbuseTest.php | 6 ++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index d8fe4302298..9dae438088e 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -64,12 +64,12 @@ public function setup() : void throw new Exception("You need to create database before running timelimit setup"); } $this->db->createCollection(TimeLimit::COLLECTION); - $this->db->createAttribute(TimeLimit::COLLECTION, '_key', Database::VAR_STRING, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, '_time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); + $this->db->createAttribute(TimeLimit::COLLECTION, 'key', Database::VAR_STRING, Database::LENGTH_KEY, true); + $this->db->createAttribute(TimeLimit::COLLECTION, 'time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); - $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['_key', '_time']); - $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['_key', '_time']); - $this->db->createIndex(TimeLimit::COLLECTION, 'index2', Database::INDEX_KEY, ['_time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['key', 'time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['key', 'time']); + $this->db->createIndex(TimeLimit::COLLECTION, 'index2', Database::INDEX_KEY, ['time']); } /** @@ -137,8 +137,8 @@ protected function count(string $key, int $time): int Authorization::disable(); $result = $this->db->find(TimeLimit::COLLECTION, [ - new Query('_key', Query::TYPE_EQUAL, [$key]), - new Query('_time', Query::TYPE_EQUAL, [$time]), + new Query('key', Query::TYPE_EQUAL, [$key]), + new Query('time', Query::TYPE_EQUAL, [$time]), ], 1); Authorization::reset(); @@ -169,15 +169,15 @@ protected function hit(string $key, int $time): void Authorization::disable(); $existing = $this->db->find(TimeLimit::COLLECTION, [ - new Query('_key', Query::TYPE_EQUAL, [$key]), - new Query('_time', Query::TYPE_EQUAL, [$time]), + new Query('key', Query::TYPE_EQUAL, [$key]), + new Query('time', Query::TYPE_EQUAL, [$time]), ], 1); $data = [ '$read' => [], '$write' => [], - '_key' => $key, - '_time' => $time, + 'key' => $key, + 'time' => $time, '_count' => 1, '$collection' => TimeLimit::COLLECTION, ]; @@ -227,7 +227,7 @@ public function cleanup(int $timestamp): bool Authorization::disable(); do { $documents = $this->db->find(TimeLimit::COLLECTION, [ - new Query('_time', Query::TYPE_LESSER, [$timestamp]), + new Query('time', Query::TYPE_LESSER, [$timestamp]), ]); foreach ($documents as $document) { diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index bfd693d4f57..11f35539505 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -28,7 +28,6 @@ class AbuseTest extends TestCase * @var Abuse */ protected $abuse = null; - protected $initialized = false; public function setUp(): void { @@ -48,12 +47,11 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setNamespace('namespace'); - + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - if(!$this->initialized) { + if(!$db->exists()) { $db->create(); $adapter->setup(); - $this->initialized = true; } $adapter->setParam('{{ip}}', '127.0.0.1'); From 9a4d7995d2c0539d050151d665c9ad6db264e733 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 16:34:57 +0545 Subject: [PATCH 082/470] upgraded dependency and remove _ --- composer.json | 2 +- src/Abuse/Adapters/TimeLimit.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 7184bce4fd8..3c991ce57fa 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.2.*" + "utopia-php/database": "0.3.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 9dae438088e..a6c94034459 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -66,7 +66,7 @@ public function setup() : void $this->db->createCollection(TimeLimit::COLLECTION); $this->db->createAttribute(TimeLimit::COLLECTION, 'key', Database::VAR_STRING, Database::LENGTH_KEY, true); $this->db->createAttribute(TimeLimit::COLLECTION, 'time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, '_count', Database::VAR_INTEGER, 11, true); + $this->db->createAttribute(TimeLimit::COLLECTION, 'count', Database::VAR_INTEGER, 11, true); $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['key', 'time']); $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['key', 'time']); $this->db->createIndex(TimeLimit::COLLECTION, 'index2', Database::INDEX_KEY, ['time']); @@ -143,7 +143,7 @@ protected function count(string $key, int $time): int Authorization::reset(); if (\count($result) === 1) { - $result = $result[0]->getAttribute('_count',0); + $result = $result[0]->getAttribute('count',0); } else { $result = 0; } @@ -178,14 +178,14 @@ protected function hit(string $key, int $time): void '$write' => [], 'key' => $key, 'time' => $time, - '_count' => 1, + 'count' => 1, '$collection' => TimeLimit::COLLECTION, ]; if (\count($existing) === 1) { //update $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ - '_count' => $existing[0]->getAttribute('_count',0) + 1, + 'count' => $existing[0]->getAttribute('count',0) + 1, '$id' => $existing[0]->getId(), ]))); } else { From 6de9acf785deb8597ea071d1e9632e12bd0847f5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 16:37:08 +0545 Subject: [PATCH 083/470] update cleanup --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index a6c94034459..0cb19766375 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -233,7 +233,7 @@ public function cleanup(int $timestamp): bool foreach ($documents as $document) { $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); } - } while(\count($documents) > 0); + } while(!empty($documents) > 0); Authorization::reset(); return true; From a7de2639b107af742c21ddd80b54e9edfb540fce Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 10 Jun 2021 16:50:08 +0545 Subject: [PATCH 084/470] fix check --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 0cb19766375..743d3ee7c87 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -233,7 +233,7 @@ public function cleanup(int $timestamp): bool foreach ($documents as $document) { $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); } - } while(!empty($documents) > 0); + } while(!empty($documents)); Authorization::reset(); return true; From 351dba60714321fabcd5028fdf7325f18f343018 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 13 Jun 2021 13:26:20 +0545 Subject: [PATCH 085/470] order queries by _id descending --- src/Abuse/Adapters/TimeLimit.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 743d3ee7c87..d94bb9803bf 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -139,7 +139,7 @@ protected function count(string $key, int $time): int $result = $this->db->find(TimeLimit::COLLECTION, [ new Query('key', Query::TYPE_EQUAL, [$key]), new Query('time', Query::TYPE_EQUAL, [$time]), - ], 1); + ], 1, 0, ['_id'], ['DESC']); Authorization::reset(); if (\count($result) === 1) { @@ -171,7 +171,7 @@ protected function hit(string $key, int $time): void $existing = $this->db->find(TimeLimit::COLLECTION, [ new Query('key', Query::TYPE_EQUAL, [$key]), new Query('time', Query::TYPE_EQUAL, [$time]), - ], 1); + ], 1, 0, ['_id'], ['DESC']); $data = [ '$read' => [], @@ -210,7 +210,7 @@ protected function hit(string $key, int $time): void public function getLogs(int $offset, int $limit): array { Authorization::disable(); - $result = $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset); + $result = $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset, ['_id'], ['DESC']); Authorization::reset(); return $result; } From c71e76832bcade49d40a5da3d221c271e7300f88 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 18 Jun 2021 11:44:36 +0545 Subject: [PATCH 086/470] tests using docker-compose --- .travis.yml | 27 ++++++++++++++++++--------- Dockerfile-php7 | 27 +++++++++++++++++++++++++++ Dockerfile-php8 | 27 +++++++++++++++++++++++++++ docker-compose.yml | 30 ++++++++++++++++++++++++++++++ tests/Abuse/AbuseTest.php | 16 +++++++++------- 5 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 Dockerfile-php7 create mode 100644 Dockerfile-php8 create mode 100644 docker-compose.yml diff --git a/.travis.yml b/.travis.yml index 34f1eb261ab..b398118f8b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,28 @@ -language: php +dist: xenial -php: -- 7.4 -- 8.0 -- nightly +arch: + - amd64 + +os: linux + +language: shell services: -- mysql +- docker notifications: email: - team@appwrite.io -before_script: composer install --ignore-platform-reqs +before_script: docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist + +install: +- docker-compose up -d +- sleep 10 script: -- vendor/bin/phpunit --configuration phpunit.xml -- vendor/bin/psalm --show-info=true +- docker ps +- docker-compose exec php7.4 vendor/bin/phpunit --configuration phpunit.xml tests +- docker-compose exec php7.4 vendor/bin/psalm --show-info=true +- docker-compose exec php8 vendor/bin/phpunit --configuration phpunit.xml tests +- docker-compose exec php8 vendor/bin/psalm --show-info=true diff --git a/Dockerfile-php7 b/Dockerfile-php7 new file mode 100644 index 00000000000..b7b776db74a --- /dev/null +++ b/Dockerfile-php7 @@ -0,0 +1,27 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY ./composer.json /src/ +COPY ./composer.lock /src/ + +RUN composer update --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM php:7.4-cli-alpine as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml +COPY ./psalm.xml /code/psalm.xml + +CMD [ "tail", "-f", "/dev/null" ] diff --git a/Dockerfile-php8 b/Dockerfile-php8 new file mode 100644 index 00000000000..8b818334051 --- /dev/null +++ b/Dockerfile-php8 @@ -0,0 +1,27 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY composer.lock /src/ +COPY composer.json /src/ + +RUN composer update --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM php:8.0-cli-alpine as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml +COPY ./psalm.xml /code/psalm.xml + +CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..787904824cd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3' + +services: + mysql: + image: mysql:8 + environment: + - MYSQL_ROOT_PASSWORD=password + networks: + - abuse + + php7.4: + build: + context: . + dockerfile: ./Dockerfile-php7 + networks: + - abuse + depends_on: + - mysql + + php8: + build: + context: . + dockerfile: ./Dockerfile-php8 + networks: + - abuse + depends_on: + - mysql + +networks: + abuse: \ No newline at end of file diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 11f35539505..2a80ea1a69e 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -32,13 +32,15 @@ class AbuseTest extends TestCase public function setUp(): void { // Limit login attempts to 3 time in 5 minutes time frame - $dbHost = '127.0.0.1'; - $dbUser = 'travis'; - $dbPass = ''; - - $pdo = new PDO("mysql:host={$dbHost};", $dbUser, $dbPass, array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - PDO::ATTR_TIMEOUT => 5, // Seconds + $dbHost = 'mysql'; + $dbUser = 'root'; + $dbPort = '3306'; + $dbPass = 'password'; + + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true )); // Connection settings From 8486a4f95248e5a9fb1fe9b650278b264857e929 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 18 Jun 2021 12:06:55 +0545 Subject: [PATCH 087/470] composer lock --- .gitignore | 1 - composer.lock | 4060 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4060 insertions(+), 1 deletion(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 8924c8079af..e244eda0bc4 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -composer.lock /vendor/ /.idea/ \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000000..f7ba6ef59e7 --- /dev/null +++ b/composer.lock @@ -0,0 +1,4060 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e9251622f9462aa5c40deff7a203c587", + "packages": [ + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.2", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-24T07:46:03+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8.0", + "php": "^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0|^8.5|^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" + }, + "time": "2021-02-04T16:20:16+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.8.1", + "jean85/pretty-package-versions": "^1.2", + "php": "^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.19" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5, <3.5.5", + "symfony/phpunit-bridge": "5.x-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "MongoDB\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + }, + "time": "2020-11-25T12:26:02+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "utopia-php/cache", + "version": "0.4.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/cache.git", + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8c48eff73219c8c1ac2807909f0a38f3480c8938", + "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-redis": "*", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Cache\\": "src/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "description": "A simple cache library to manage application cache storing, loading and purging", + "keywords": [ + "cache", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/cache/issues", + "source": "https://github.com/utopia-php/cache/tree/0.4.1" + }, + "time": "2021-04-29T18:41:43+00:00" + }, + { + "name": "utopia-php/database", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/database.git", + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "shasum": "" + }, + "require": { + "ext-mongodb": "*", + "ext-pdo": "*", + "ext-redis": "*", + "mongodb/mongodb": "1.8.0", + "php": ">=7.1", + "utopia-php/cache": "0.4.*", + "utopia-php/framework": "0.*.*" + }, + "require-dev": { + "fakerphp/faker": "^1.14", + "phpunit/phpunit": "^9.4", + "utopia-php/cli": "^0.11.0", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Database\\": "src/Database" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Brandon Leckemby", + "email": "brandon@appwrite.io" + } + ], + "description": "A simple library to manage application persistency using multiple database adapters", + "keywords": [ + "database", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.3.2" + }, + "time": "2021-06-12T18:26:26+00:00" + }, + { + "name": "utopia-php/framework", + "version": "0.14.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/framework.git", + "reference": "632113288bebe41cbef79f0d355bd91609767b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/632113288bebe41cbef79f0d355bd91609767b8c", + "reference": "632113288bebe41cbef79f0d355bd91609767b8c", + "shasum": "" + }, + "require": { + "php": ">=7.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.4", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], + "description": "A simple, light and advanced PHP framework", + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/framework/issues", + "source": "https://github.com/utopia-php/framework/tree/0.14.1" + }, + "time": "2021-05-21T06:41:45+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-01-10T17:06:37+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-05-24T12:41:47+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.4.6", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "f27e06cd9675801df441b3656569b328e04aa37c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c", + "reference": "f27e06cd9675801df441b3656569b328e04aa37c", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/1.4.6" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-03-25T17:01:18+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" + }, + "time": "2021-02-22T14:02:09+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "ba09f0e456d4f00cef84e012da5715625594407c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/ba09f0e456d4f00cef84e012da5715625594407c", + "reference": "ba09f0e456d4f00cef84e012da5715625594407c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v3.1.1" + }, + "time": "2020-11-02T19:19:54+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + }, + "time": "2021-05-03T19:11:20+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89ff45ea9d70e35522fb6654a2ebc221158de276", + "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.2", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.5" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-05T04:49:07+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/console", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-12T09:42:48+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-04-01T10:43:52+00:00" + }, + { + "name": "symfony/string", + "version": "v5.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", + "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-06T09:51:56+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.1", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.4", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", + "nikic/php-parser": "^4.10.1", + "openlss/lib-array2xml": "^1.0", + "php": "^7.3|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", + "webmozart/glob": "^4.1", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/amp": "^2.4.2", + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0.0", + "ext-curl": "*", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.13", + "slevomat/coding-standard": "^5.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-igbinary": "^2.0.5" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + }, + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.0.1" + }, + "time": "2020-10-20T13:40:17+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" + }, + { + "name": "webmozart/glob", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/06358fafde0f32edb4513f4fd88fe113a40c90ee", + "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0", + "webmozart/path-util": "^2.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.0", + "symfony/filesystem": "^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.3.0" + }, + "time": "2021-01-21T06:17:15+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4", + "ext-pdo": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} From b9989f6a1c09f1e1773caa05881c17a610808962 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 23 Jun 2021 16:39:37 +0200 Subject: [PATCH 088/470] fix(timelimit): remove pdo type --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b1fe5c1d20f..5c827323ff4 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -309,7 +309,7 @@ public function time(): int return $this->time; } - protected function getPDO(): PDO + protected function getPDO() { return \call_user_func($this->connection); } From e04c303992f592cd1811f965d4b162e3a9e80090 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 23 Jun 2021 16:59:17 +0200 Subject: [PATCH 089/470] add phpdoc block to getPDO method --- src/Abuse/Adapters/TimeLimit.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 5c827323ff4..53b2eeeebd5 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -309,6 +309,11 @@ public function time(): int return $this->time; } + /** + * Get PDO connection. + * + * @return PDO + */ protected function getPDO() { return \call_user_func($this->connection); From 5870fa386fd63ee45f7aa8449fc3859aa3c87b39 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 28 Jun 2021 09:35:20 +0200 Subject: [PATCH 090/470] fix(time-limit): passing db instead of callback --- src/Abuse/Adapters/TimeLimit.php | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 53b2eeeebd5..b8a75367d1c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -9,9 +9,9 @@ class TimeLimit implements Adapter { /** - * @var callable + * @var PDO */ - protected $connection; + protected $db; /** * @var string @@ -42,14 +42,14 @@ class TimeLimit implements Adapter * @param string $key * @param int $time * @param int $limit - * @param callable $connection + * @param PDO $db */ - public function __construct(string $key, int $limit, int $time, callable $connection) + public function __construct(string $key, int $limit, int $time, $db) { $this->key = $key; $this->time = (int)\date('U', (int)(\floor(\time() / $time)) * $time); $this->limit = $limit; - $this->connection = $connection; + $this->db = db; } /** @@ -157,7 +157,7 @@ protected function count(string $key, int $time): int return $this->count; } - $st = $this->getPDO()->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` + $st = $this->db->prepare('SELECT _count FROM `' . $this->getNamespace() . '.abuse.abuse` WHERE _key = :key AND _time = :time LIMIT 1; '); @@ -189,7 +189,7 @@ protected function hit(string $key, int $time): void return; } - $st = $this->getPDO()->prepare('INSERT INTO `' . $this->getNamespace() . '.abuse.abuse` + $st = $this->db->prepare('INSERT INTO `' . $this->getNamespace() . '.abuse.abuse` SET _key = :key, _time = :time, _count = 1 ON DUPLICATE KEY UPDATE _count = _count + 1; '); @@ -214,7 +214,7 @@ protected function hit(string $key, int $time): void */ public function getLogs(int $offset, int $limit): array { - $st = $this->getPDO()->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`; + $st = $this->db->prepare('SELECT * FROM `' . $this->getNamespace() . '.abuse.abuse`; LIMIT :offset, :limit '); $st->bindValue(':offset', $offset, PDO::PARAM_INT); @@ -236,7 +236,7 @@ public function getLogs(int $offset, int $limit): array { */ public function cleanup(int $timestamp):bool { - $st = $this->getPDO()->prepare('DELETE + $st = $this->db->prepare('DELETE FROM `'.$this->getNamespace().'.abuse.abuse` WHERE `_time` < :timestamp'); @@ -308,14 +308,4 @@ public function time(): int { return $this->time; } - - /** - * Get PDO connection. - * - * @return PDO - */ - protected function getPDO() - { - return \call_user_func($this->connection); - } } From b5f4017f24d4831018435f031f202425f9cf8c5d Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 28 Jun 2021 09:46:51 +0200 Subject: [PATCH 091/470] Update AbuseTest.php --- tests/Abuse/AbuseTest.php | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 308bb4131f3..6f36be82c48 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -28,24 +28,22 @@ class AbuseTest extends TestCase public function setUp():void { - // Limit login attempts to 3 time in 5 minutes time frame - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), function () { - $dbHost = '127.0.0.1'; - $dbUser = 'travis'; - $dbPass = ''; - $dbName = 'abuse'; + $dbHost = '127.0.0.1'; + $dbUser = 'travis'; + $dbPass = ''; + $dbName = 'abuse'; - $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - PDO::ATTR_TIMEOUT => 5 // Seconds - )); + $pdo = new PDO("mysql:host={$dbHost};dbname={$dbName}", $dbUser, $dbPass, array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + PDO::ATTR_TIMEOUT => 5 // Seconds + )); - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions + // Connection settings + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Handle all errors with exceptions - return $pdo; - }); + // Limit login attempts to 3 time in 5 minutes time frame + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $pdo); $adapter ->setNamespace('namespace') // DB table namespace From 78a475cc88cdbdf95285db92b558a1299dd1c876 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 28 Jun 2021 10:08:17 +0200 Subject: [PATCH 092/470] Update TimeLimit.php --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b8a75367d1c..cf0d5eed6bd 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -49,7 +49,7 @@ public function __construct(string $key, int $limit, int $time, $db) $this->key = $key; $this->time = (int)\date('U', (int)(\floor(\time() / $time)) * $time); $this->limit = $limit; - $this->db = db; + $this->db = $db; } /** From 134362409402183f5e4f44dcdb7c6def9a473565 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Thu, 1 Jul 2021 17:15:19 +0300 Subject: [PATCH 093/470] Update .travis.yml --- .travis.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b398118f8b5..33680cc2689 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,15 +7,24 @@ os: linux language: shell -services: -- docker - notifications: email: - team@appwrite.io before_script: docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist +before_install: +- curl -fsSL https://get.docker.com | sh +- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json +- mkdir -p $HOME/.docker +- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json +- sudo service docker start +- > + if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then + echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin + fi +- docker --version + install: - docker-compose up -d - sleep 10 From 44ed62a068fdb5b1af9b511394691aaac921423d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 4 Jul 2021 11:38:44 +0545 Subject: [PATCH 094/470] update db lib --- composer.json | 2 +- composer.lock | 52 +++++++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 3c991ce57fa..9b2862a1d46 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.3.*" + "utopia-php/database": "0.5.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index f7ba6ef59e7..88d87e69315 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": "e9251622f9462aa5c40deff7a203c587", + "content-hash": "e2445e824ea0643057c76f6a2735fa24", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.3.2", + "version": "0.5.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56" + "reference": "e050e51060df72eff3af9fc24fc95a41ca9a2096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", - "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e050e51060df72eff3af9fc24fc95a41ca9a2096", + "reference": "e050e51060df72eff3af9fc24fc95a41ca9a2096", "shasum": "" }, "require": { @@ -397,9 +397,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.3.2" + "source": "https://github.com/utopia-php/database/tree/0.5.0" }, - "time": "2021-06-12T18:26:26+00:00" + "time": "2021-07-03T16:49:44+00:00" }, { "name": "utopia-php/framework", @@ -1081,16 +1081,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.5", + "version": "v4.11.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fe14cf3672a149364fb66dfe11bf6549af899f94", + "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94", "shasum": "" }, "require": { @@ -1131,9 +1131,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.11.0" }, - "time": "2021-05-03T19:11:20+00:00" + "time": "2021-07-03T13:36:55+00:00" }, { "name": "openlss/lib-array2xml", @@ -1844,16 +1844,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.5", + "version": "9.5.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276" + "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/89ff45ea9d70e35522fb6654a2ebc221158de276", - "reference": "89ff45ea9d70e35522fb6654a2ebc221158de276", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", + "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", "shasum": "" }, "require": { @@ -1883,7 +1883,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.2", + "sebastian/type": "^2.3.4", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -1931,7 +1931,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.6" }, "funding": [ { @@ -1943,7 +1943,7 @@ "type": "github" } ], - "time": "2021-06-05T04:49:07+00:00" + "time": "2021-06-23T05:14:38+00:00" }, { "name": "psr/container", @@ -3656,16 +3656,16 @@ }, { "name": "symfony/string", - "version": "v5.3.2", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", "shasum": "" }, "require": { @@ -3719,7 +3719,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.2" + "source": "https://github.com/symfony/string/tree/v5.3.3" }, "funding": [ { @@ -3735,7 +3735,7 @@ "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2021-06-27T11:44:38+00:00" }, { "name": "theseer/tokenizer", From a4b47fc7a889628077a32d4e2e8c8b15165b74ae Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 4 Jul 2021 11:38:59 +0545 Subject: [PATCH 095/470] upgrade db use new create collection with attributes and indexes together --- src/Abuse/Adapters/TimeLimit.php | 63 ++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index d94bb9803bf..bc5b97883b2 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -63,13 +63,62 @@ public function setup() : void if (!$this->db->exists()) { throw new Exception("You need to create database before running timelimit setup"); } - $this->db->createCollection(TimeLimit::COLLECTION); - $this->db->createAttribute(TimeLimit::COLLECTION, 'key', Database::VAR_STRING, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, 'time', Database::VAR_INTEGER, Database::LENGTH_KEY, true); - $this->db->createAttribute(TimeLimit::COLLECTION, 'count', Database::VAR_INTEGER, 11, true); - $this->db->createIndex(TimeLimit::COLLECTION, 'unique1', Database::INDEX_UNIQUE, ['key', 'time']); - $this->db->createIndex(TimeLimit::COLLECTION, 'index1', Database::INDEX_KEY, ['key', 'time']); - $this->db->createIndex(TimeLimit::COLLECTION, 'index2', Database::INDEX_KEY, ['time']); + + $attributes = [ + new Document([ + '$id' => 'key', + 'type' => Database::VAR_STRING, + 'size' => Database::LENGTH_KEY, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'time', + 'type' => Database::VAR_INTEGER, + 'size' => 0, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'count', + 'type' => Database::VAR_INTEGER, + 'size' => 11, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => [], + ]), + ]; + + $indexes = [ + new Document([ + '$Id' => 'unique1', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['key', 'time'], + 'lengths' => [], + 'orders' => [], + ]), + new Document([ + '$Id' => 'index1', + 'type' => Database::INDEX_KEY, + 'attributes' => ['key', 'time'], + 'lengths' => [], + 'orders' => [], + ]), + new Document([ + '$Id' => 'index2', + 'type' => Database::INDEX_KEY, + 'attributes' => ['time'], + 'lengths' => [], + 'orders' => [], + ]), + ]; + + $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); } /** From 9742a72dfecb27e74e2d8d8018b7bdbec6637de6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 4 Jul 2021 11:51:38 +0545 Subject: [PATCH 096/470] fix index names --- src/Abuse/Adapters/TimeLimit.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 1bf91169a4a..1db80ed6a76 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -96,21 +96,21 @@ public function setup() : void $indexes = [ new Document([ - '$Id' => 'unique1', + '$id' => 'unique1', 'type' => Database::INDEX_UNIQUE, 'attributes' => ['key', 'time'], 'lengths' => [], 'orders' => [], ]), new Document([ - '$Id' => 'index1', + '$id' => 'index1', 'type' => Database::INDEX_KEY, 'attributes' => ['key', 'time'], 'lengths' => [], 'orders' => [], ]), new Document([ - '$Id' => 'index2', + '$id' => 'index2', 'type' => Database::INDEX_KEY, 'attributes' => ['time'], 'lengths' => [], From e6cab8c15df11b3b67217ed9b3ba634b7d9676c3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 3 Aug 2021 15:11:15 -0400 Subject: [PATCH 097/470] Update utopia-php/database from 0.5 to 0.6 --- composer.json | 2 +- composer.lock | 147 +++++++++++++++++++++++++------------------------- 2 files changed, 75 insertions(+), 74 deletions(-) diff --git a/composer.json b/composer.json index 9b2862a1d46..ef84496f624 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.5.*" + "utopia-php/database": "0.6.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 88d87e69315..199d3168939 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": "e2445e824ea0643057c76f6a2735fa24", + "content-hash": "a5ae1fbf6d82624f236efcccd417e417", "packages": [ { "name": "composer/package-versions-deprecated", @@ -204,16 +204,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { @@ -267,7 +267,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" }, "funding": [ { @@ -283,7 +283,7 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-07-28T13:41:28+00:00" }, { "name": "utopia-php/cache", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.5.0", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e050e51060df72eff3af9fc24fc95a41ca9a2096" + "reference": "561adc215fce3bd3b8c3ebb971ca354fb1526f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e050e51060df72eff3af9fc24fc95a41ca9a2096", - "reference": "e050e51060df72eff3af9fc24fc95a41ca9a2096", + "url": "https://api.github.com/repos/utopia-php/database/zipball/561adc215fce3bd3b8c3ebb971ca354fb1526f26", + "reference": "561adc215fce3bd3b8c3ebb971ca354fb1526f26", "shasum": "" }, "require": { @@ -397,22 +397,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.5.0" + "source": "https://github.com/utopia-php/database/tree/0.6.0" }, - "time": "2021-07-03T16:49:44+00:00" + "time": "2021-08-03T15:13:48+00:00" }, { "name": "utopia-php/framework", - "version": "0.14.1", + "version": "0.17.3", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "632113288bebe41cbef79f0d355bd91609767b8c" + "reference": "0274f6b3e49db2af0d702edf278ec7504dc99878" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/632113288bebe41cbef79f0d355bd91609767b8c", - "reference": "632113288bebe41cbef79f0d355bd91609767b8c", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/0274f6b3e49db2af0d702edf278ec7504dc99878", + "reference": "0274f6b3e49db2af0d702edf278ec7504dc99878", "shasum": "" }, "require": { @@ -446,35 +446,35 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.14.1" + "source": "https://github.com/utopia-php/framework/tree/0.17.3" }, - "time": "2021-05-21T06:41:45+00:00" + "time": "2021-08-03T13:57:01+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.5.2", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9" + "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/efca2b32a7580087adb8aabbff6be1dc1bb924a9", - "reference": "efca2b32a7580087adb8aabbff6be1dc1bb924a9", + "url": "https://api.github.com/repos/amphp/amp/zipball/caa95edeb1ca1bf7532e9118ede4a3c3126408cc", + "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc", "shasum": "" }, "require": { - "php": ">=7" + "php": ">=7.1" }, "require-dev": { "amphp/php-cs-fixer-config": "dev-master", "amphp/phpunit-util": "^1", "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6.0.9 | ^7", + "phpunit/phpunit": "^7 | ^8 | ^9", "psalm/phar": "^3.11@dev", "react/promise": "^2" }, @@ -531,7 +531,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.5.2" + "source": "https://github.com/amphp/amp/tree/v2.6.0" }, "funding": [ { @@ -539,7 +539,7 @@ "type": "github" } ], - "time": "2021-01-10T17:06:37+00:00" + "time": "2021-07-16T20:06:06+00:00" }, { "name": "amphp/byte-stream", @@ -1081,16 +1081,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.11.0", + "version": "v4.12.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94" + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fe14cf3672a149364fb66dfe11bf6549af899f94", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", "shasum": "" }, "require": { @@ -1131,9 +1131,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.11.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" }, - "time": "2021-07-03T13:36:55+00:00" + "time": "2021-07-21T10:44:31+00:00" }, { "name": "openlss/lib-array2xml", @@ -1190,16 +1190,16 @@ }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -1244,9 +1244,9 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", @@ -1844,16 +1844,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.6", + "version": "9.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb" + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", - "reference": "fb9b8333f14e3dce976a60ef6a7e05c7c7ed8bfb", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/191768ccd5c85513b4068bdbe99bb6390c7d54fb", + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb", "shasum": "" }, "require": { @@ -1865,7 +1865,7 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", @@ -1931,7 +1931,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.8" }, "funding": [ { @@ -1943,7 +1943,7 @@ "type": "github" } ], - "time": "2021-06-23T05:14:38+00:00" + "time": "2021-07-31T15:17:34+00:00" }, { "name": "psr/container", @@ -3009,16 +3009,16 @@ }, { "name": "symfony/console", - "version": "v5.3.2", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", "shasum": "" }, "require": { @@ -3026,11 +3026,12 @@ "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2", "symfony/string": "^5.1" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", @@ -3038,10 +3039,10 @@ "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2", "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/event-dispatcher": "^4.4|^5.0", @@ -3087,7 +3088,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.2" + "source": "https://github.com/symfony/console/tree/v5.3.6" }, "funding": [ { @@ -3103,7 +3104,7 @@ "type": "tidelift" } ], - "time": "2021-06-12T09:42:48+00:00" + "time": "2021-07-27T19:10:22+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3253,16 +3254,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", "shasum": "" }, "require": { @@ -3314,7 +3315,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" }, "funding": [ { @@ -3330,7 +3331,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-intl-normalizer", @@ -3418,16 +3419,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "shasum": "" }, "require": { @@ -3478,7 +3479,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" }, "funding": [ { @@ -3494,7 +3495,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-php73", @@ -3739,16 +3740,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -3777,7 +3778,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -3785,7 +3786,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { "name": "vimeo/psalm", From 276b21524cc19876e82b8bd903e6163608b84623 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 13 Aug 2021 09:46:36 +0200 Subject: [PATCH 098/470] chore(composer): update database to 0.7.0 --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index ef84496f624..f2759284403 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.6.*" + "utopia-php/database": "0.7.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 199d3168939..3ed57934d9e 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": "a5ae1fbf6d82624f236efcccd417e417", + "content-hash": "0190199351723899601d2541facb4652", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.6.0", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "561adc215fce3bd3b8c3ebb971ca354fb1526f26" + "reference": "46c4a99347397e362a9429826e1888b0aefb2056" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/561adc215fce3bd3b8c3ebb971ca354fb1526f26", - "reference": "561adc215fce3bd3b8c3ebb971ca354fb1526f26", + "url": "https://api.github.com/repos/utopia-php/database/zipball/46c4a99347397e362a9429826e1888b0aefb2056", + "reference": "46c4a99347397e362a9429826e1888b0aefb2056", "shasum": "" }, "require": { @@ -397,9 +397,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.6.0" + "source": "https://github.com/utopia-php/database/tree/0.7.0" }, - "time": "2021-08-03T15:13:48+00:00" + "time": "2021-08-10T19:09:58+00:00" }, { "name": "utopia-php/framework", @@ -4057,5 +4057,5 @@ "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } From dfaa87b4e475354107bc9f8a5887f86cd71af11a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 16 Aug 2021 21:18:22 +0300 Subject: [PATCH 099/470] Updated DB lib --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f2759284403..8065fd2a315 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=7.4", "ext-pdo": "*", - "utopia-php/database": "0.7.*" + "utopia-php/database": ">=0.6 <1.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 3ed57934d9e..4b1ae02ce9a 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": "0190199351723899601d2541facb4652", + "content-hash": "ba32ecede696607285c7360b2422436d", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "46c4a99347397e362a9429826e1888b0aefb2056" + "reference": "2645c150267aaf73c70fb8a8d1a74430c9e6f336" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/46c4a99347397e362a9429826e1888b0aefb2056", - "reference": "46c4a99347397e362a9429826e1888b0aefb2056", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2645c150267aaf73c70fb8a8d1a74430c9e6f336", + "reference": "2645c150267aaf73c70fb8a8d1a74430c9e6f336", "shasum": "" }, "require": { @@ -397,9 +397,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.7.0" + "source": "https://github.com/utopia-php/database/tree/0.8.0" }, - "time": "2021-08-10T19:09:58+00:00" + "time": "2021-08-16T17:19:07+00:00" }, { "name": "utopia-php/framework", From f6e47f4a42c38582e4c3dd55ce40df9d3ad0163c Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:51:01 +0530 Subject: [PATCH 100/470] Create bug.yaml --- .github/ISSUE_TEMPLATE/bug.yaml | 76 +++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yaml diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 00000000000..04cc6104f0b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,76 @@ +name: "🐛 Bug Report" +description: "Submit a bug report to help us improve" +title: "🐛 Bug Report: " +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out our bug report form 🙏 + - type: textarea + id: steps-to-reproduce + validations: + required: true + attributes: + label: "👟 Reproduction steps" + description: "How do you trigger this bug? Please walk us through it step by step." + placeholder: "When I ..." + - type: textarea + id: expected-behavior + validations: + required: true + attributes: + label: "👍 Expected behavior" + description: "What did you think would happen?" + placeholder: "It should ..." + - type: textarea + id: actual-behavior + validations: + required: true + attributes: + label: "👎 Actual Behavior" + description: "What did actually happen? Add screenshots, if applicable." + placeholder: "It actually ..." + - type: dropdown + id: php-clamav-version + attributes: + label: "🎲 Appwrite ClamAV PHP Client version" + description: "What version of Appwrite are you running?" + options: + - Version 0.6.x + - Version 0.5.x + - Version 0.4.x + - Version 0.3.x + - Version 0.2.x + - Version 0.1.x + - Different version (specify in environment) + validations: + required: true + - type: dropdown + id: operating-system + attributes: + label: "💻 Operating system" + description: "What OS is your server / device running on?" + options: + - Linux + - MacOS + - Windows + - Something else + validations: + required: true + - type: textarea + id: enviromnemt + validations: + required: false + attributes: + label: "🧱 Your Environment" + description: "Is your environment customized in any way?" + placeholder: "I use Cloudflare for ..." + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "👀 Have you spent some time to check if this issue has been raised before?" + description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" + options: + - label: "I checked and didn't find similar issue" + required: true From fca23a9dead10da24d8571ee57c98e936e1e691b Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:52:09 +0530 Subject: [PATCH 101/470] Update bug.yaml --- .github/ISSUE_TEMPLATE/bug.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 04cc6104f0b..7b53698532b 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -32,9 +32,9 @@ body: description: "What did actually happen? Add screenshots, if applicable." placeholder: "It actually ..." - type: dropdown - id: php-clamav-version + id: abuse-version attributes: - label: "🎲 Appwrite ClamAV PHP Client version" + label: "🎲 Utopia Abuse version" description: "What version of Appwrite are you running?" options: - Version 0.6.x From b6d8262cd0b08fd1eb5eb30122e2498b7f0d069c Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:53:09 +0530 Subject: [PATCH 102/470] Update bug.yaml --- .github/ISSUE_TEMPLATE/bug.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 7b53698532b..2622aba5f3d 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -35,7 +35,7 @@ body: id: abuse-version attributes: label: "🎲 Utopia Abuse version" - description: "What version of Appwrite are you running?" + description: "What version of Utopia Abuse are you running?" options: - Version 0.6.x - Version 0.5.x From b2b1d6cf6318d9dc403bb37765d93aedba055fe0 Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:56:00 +0530 Subject: [PATCH 103/470] Create documentation.yaml --- .github/ISSUE_TEMPLATE/documentation.yaml | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/documentation.yaml diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml new file mode 100644 index 00000000000..6d1a73f06cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -0,0 +1,33 @@ +name: "📚 Documentation" +description: "Report an issue related to documentation" +title: "📚 Documentation: " +labels: [documentation] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out our documentation update request form 🙏 + - type: textarea + id: issue-description + validations: + required: true + attributes: + label: "💭 Description" + description: "A clear and concise description of what the issue is." + placeholder: "Documentation should not ..." + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "👀 Have you spent some time to check if this issue has been raised before?" + description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + description: "This is our [Code of Conduct](https://github.com/appwrite/appwrite/blob/master/CODE_OF_CONDUCT.md)." + label: "🏢 Have you read the Code of Conduct?" + options: + - label: "I read the Code of Conduct" + required: true From d1bc823c4ae0fe3f409e546c735960802d7b1183 Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:56:23 +0530 Subject: [PATCH 104/470] Create feature.yaml --- .github/ISSUE_TEMPLATE/feature.yaml | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature.yaml diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml new file mode 100644 index 00000000000..8bd9a3dcc8f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -0,0 +1,33 @@ +name: 🚀 Feature +description: "Submit a proposal for a new feature" +title: "🚀 Feature: " +labels: [feature] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out our feature request form 🙏 + - type: textarea + id: feature-description + validations: + required: true + attributes: + label: "🔖 Feature description" + description: "A clear and concise description of what the feature is." + placeholder: "You should add ..." + - type: textarea + id: pitch + validations: + required: true + attributes: + label: "🎤 Pitch" + description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable." + placeholder: "In my use-case, ..." + - type: checkboxes + id: no-duplicate-issues + attributes: + label: "👀 Have you spent some time to check if this issue has been raised before?" + description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" + options: + - label: "I checked and didn't find similar issue" + required: true From fba4889770599e781652fe9271dbe67b17dcb18d Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:11:38 +0530 Subject: [PATCH 105/470] Update bug.yaml --- .github/ISSUE_TEMPLATE/bug.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 2622aba5f3d..147e920ba7c 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -32,7 +32,7 @@ body: description: "What did actually happen? Add screenshots, if applicable." placeholder: "It actually ..." - type: dropdown - id: abuse-version + id: utopia-version attributes: label: "🎲 Utopia Abuse version" description: "What version of Utopia Abuse are you running?" @@ -74,3 +74,10 @@ body: options: - label: "I checked and didn't find similar issue" required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: "🏢 Have you read the Code of Conduct?" + options: + - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" + required: true From 8d37dca3d1540408592646ca648523cf0916e727 Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:13:05 +0530 Subject: [PATCH 106/470] Update documentation.yaml --- .github/ISSUE_TEMPLATE/documentation.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml index 6d1a73f06cf..b6189b09760 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yaml +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Thanks for taking the time to fill out our documentation update request form 🙏 + Thanks for taking the time to make our documentation better 🙏 - type: textarea id: issue-description validations: @@ -26,8 +26,7 @@ body: - type: checkboxes id: read-code-of-conduct attributes: - description: "This is our [Code of Conduct](https://github.com/appwrite/appwrite/blob/master/CODE_OF_CONDUCT.md)." label: "🏢 Have you read the Code of Conduct?" options: - - label: "I read the Code of Conduct" + - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" required: true From 5130f262e1a2311f801db18e68a1f0f3044ef81f Mon Sep 17 00:00:00 2001 From: Rahul Singh <22918499+drph4nt0m@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:13:24 +0530 Subject: [PATCH 107/470] Update feature.yaml --- .github/ISSUE_TEMPLATE/feature.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 8bd9a3dcc8f..630af44eb44 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -31,3 +31,10 @@ body: options: - label: "I checked and didn't find similar issue" required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: "🏢 Have you read the Code of Conduct?" + options: + - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" + required: true From ca655012d78597ca87329856df11283bfd6a3c06 Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Thu, 7 Oct 2021 11:20:20 +0200 Subject: [PATCH 108/470] Added guides --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 101 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..2dec654fbbc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity, expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at team@appwrite.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..29eb7940c0b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,101 @@ +# Contributing + +We would ❤️ for you to contribute to Utopia-php and help make it better! We want contributing to Utopia-php to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more. + +## How to Start? + +If you are worried or don’t know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you! + +## Code of Conduct + +Help us keep Utopia-php open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md). + +## Submit a Pull Request 🚀 + +Branch naming convention is as following + +`TYPE-ISSUE_ID-DESCRIPTION` + +example: + +``` +doc-548-submit-a-pull-request-section-to-contribution-guide +``` + +When `TYPE` can be: + +- **feat** - is a new feature +- **doc** - documentation only changes +- **cicd** - changes related to CI/CD system +- **fix** - a bug fix +- **refactor** - code change that neither fixes a bug nor adds a feature + +**All PRs must include a commit message with the changes description!** + +For the initial start, fork the project and use git clone command to download the repository to your computer. A standard procedure for working on an issue would be to: + +1. `git pull`, before creating a new branch, pull the changes from upstream. Your master needs to be up to date. + +``` +$ git pull +``` + +2. Create new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`
+ +``` +$ git checkout -b [name_of_your_new_branch] +``` + +3. Work - commit - repeat ( be sure to be in your branch ) + +4. Push changes to GitHub + +``` +$ git push origin [name_of_your_new_branch] +``` + +5. Submit your changes for review + If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button. +6. Start a Pull Request + Now submit the pull request and click on `Create pull request`. +7. Get a code review approval/reject +8. After approval, merge your PR +9. GitHub will automatically delete the branch after the merge is done. (they can still be restored). + +## Introducing New Features + +We would 💖 you to contribute to Utopia-php, but we would also like to make sure Utopia-php is as great as possible and loyal to its vision and mission statement 🙏. + +For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request. + +This will allow the Utopia-php community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision. + +This is also important for the Utopia-php lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc). + +## Other Ways to Help + +Pull requests are great, but there are many other areas where you can help Utopia-php. + +### Blogging & Speaking + +Blogging, speaking about, or creating tutorials about one of Utopia-php’s many features is great way to contribute and help our project grow. + +### Presenting at Meetups + +Presenting at meetups and conferences about your Utopia-php projects. Your unique challenges and successes in building things with Utopia-php can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help! + +### Sending Feedbacks & Reporting Bugs + +Sending feedback is a great way for us to understand your different use cases of Utopia-php better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA). + +### Submitting New Ideas + +If you think Utopia-php could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature. + +### Improving Documentation + +Submitting documentation updates, enhancements, designs, or bug fixes. Spelling or grammar fixes will be very much appreciated. + +### Helping Someone + +Searching for Utopia-php, GitHub or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Utopia-php's repo! From c802cf77e1a0d1e27644f095435e07cd1005a46b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 23 Dec 2021 16:16:18 -0500 Subject: [PATCH 109/470] Set PHP8 as minimum version --- .travis.yml | 2 -- Dockerfile-php7 | 27 --------------------------- README.md | 2 +- docker-compose.yml | 9 --------- 4 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 Dockerfile-php7 diff --git a/.travis.yml b/.travis.yml index 33680cc2689..d870ccd0a96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,5 @@ install: script: - docker ps -- docker-compose exec php7.4 vendor/bin/phpunit --configuration phpunit.xml tests -- docker-compose exec php7.4 vendor/bin/psalm --show-info=true - docker-compose exec php8 vendor/bin/phpunit --configuration phpunit.xml tests - docker-compose exec php8 vendor/bin/psalm --show-info=true diff --git a/Dockerfile-php7 b/Dockerfile-php7 deleted file mode 100644 index b7b776db74a..00000000000 --- a/Dockerfile-php7 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY ./composer.json /src/ -COPY ./composer.lock /src/ - -RUN composer update --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM php:7.4-cli-alpine as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./psalm.xml /code/psalm.xml - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/README.md b/README.md index b402a60dc05..afc9e456a76 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ if(!$abuse->check()) { ## System Requirements -Utopia Framework requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible. +Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible. ## Authors diff --git a/docker-compose.yml b/docker-compose.yml index 787904824cd..06c0d854b67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,15 +8,6 @@ services: networks: - abuse - php7.4: - build: - context: . - dockerfile: ./Dockerfile-php7 - networks: - - abuse - depends_on: - - mysql - php8: build: context: . From e4c945d1f617fe2ca83ab9b80dacb0a25885adda Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 23 Dec 2021 16:16:42 -0500 Subject: [PATCH 110/470] Test namespacing changes from database --- composer.json | 4 +- composer.lock | 348 +++++++++++++++++++++++++++----------------------- 2 files changed, 187 insertions(+), 165 deletions(-) diff --git a/composer.json b/composer.json index 8065fd2a315..e6e130192dd 100755 --- a/composer.json +++ b/composer.json @@ -15,9 +15,9 @@ "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, "require": { - "php": ">=7.4", + "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": ">=0.6 <1.0" + "utopia-php/database": "dev-feat-database-and-namespace as 0.13.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 4b1ae02ce9a..112e23feb8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ba32ecede696607285c7360b2422436d", + "content-hash": "b29a70743916a44ea12f633cb28e4802", "packages": [ { "name": "composer/package-versions-deprecated", - "version": "1.11.99.2", + "version": "1.11.99.4", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + "reference": "b174585d1fe49ceed21928a945138948cb394600" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", - "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", "shasum": "" }, "require": { @@ -61,7 +61,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" }, "funding": [ { @@ -77,7 +77,7 @@ "type": "tidelift" } ], - "time": "2021-05-24T07:46:03+00:00" + "time": "2021-09-13T08:41:34+00:00" }, { "name": "jean85/pretty-package-versions", @@ -287,16 +287,16 @@ }, { "name": "utopia-php/cache", - "version": "0.4.1", + "version": "0.4.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938" + "reference": "7334c5b182c6ccf66b21ca61808d7c6c899e2bcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/8c48eff73219c8c1ac2807909f0a38f3480c8938", - "reference": "8c48eff73219c8c1ac2807909f0a38f3480c8938", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/7334c5b182c6ccf66b21ca61808d7c6c899e2bcb", + "reference": "7334c5b182c6ccf66b21ca61808d7c6c899e2bcb", "shasum": "" }, "require": { @@ -306,7 +306,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.3", - "vimeo/psalm": "4.0.1" + "vimeo/psalm": "4.13.1" }, "type": "library", "autoload": { @@ -334,22 +334,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.4.1" + "source": "https://github.com/utopia-php/cache/tree/0.4.2" }, - "time": "2021-04-29T18:41:43+00:00" + "time": "2021-11-25T16:41:35+00:00" }, { "name": "utopia-php/database", - "version": "0.8.0", + "version": "dev-feat-database-and-namespace", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2645c150267aaf73c70fb8a8d1a74430c9e6f336" + "reference": "f4e02988c2dc4385c08dbb318c81462d5188ed8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2645c150267aaf73c70fb8a8d1a74430c9e6f336", - "reference": "2645c150267aaf73c70fb8a8d1a74430c9e6f336", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f4e02988c2dc4385c08dbb318c81462d5188ed8e", + "reference": "f4e02988c2dc4385c08dbb318c81462d5188ed8e", "shasum": "" }, "require": { @@ -357,7 +357,7 @@ "ext-pdo": "*", "ext-redis": "*", "mongodb/mongodb": "1.8.0", - "php": ">=7.1", + "php": ">=8.0", "utopia-php/cache": "0.4.*", "utopia-php/framework": "0.*.*" }, @@ -397,30 +397,30 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.8.0" + "source": "https://github.com/utopia-php/database/tree/feat-database-and-namespace" }, - "time": "2021-08-16T17:19:07+00:00" + "time": "2021-12-23T20:51:18+00:00" }, { "name": "utopia-php/framework", - "version": "0.17.3", + "version": "0.19.3", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "0274f6b3e49db2af0d702edf278ec7504dc99878" + "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/0274f6b3e49db2af0d702edf278ec7504dc99878", - "reference": "0274f6b3e49db2af0d702edf278ec7504dc99878", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/4c6c841d738cec458b73fec5aedd40fd43bd41a7", + "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7", "shasum": "" }, "require": { - "php": ">=7.3.0" + "php": ">=8.0.0" }, "require-dev": { - "phpunit/phpunit": "^9.4", - "vimeo/psalm": "4.0.1" + "phpunit/phpunit": "^9.5.10", + "vimeo/psalm": "4.13.1" }, "type": "library", "autoload": { @@ -446,24 +446,24 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.17.3" + "source": "https://github.com/utopia-php/framework/tree/0.19.3" }, - "time": "2021-08-03T13:57:01+00:00" + "time": "2021-12-17T13:04:13+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc" + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/caa95edeb1ca1bf7532e9118ede4a3c3126408cc", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc", + "url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", "shasum": "" }, "require": { @@ -531,7 +531,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.0" + "source": "https://github.com/amphp/amp/tree/v2.6.1" }, "funding": [ { @@ -539,7 +539,7 @@ "type": "github" } ], - "time": "2021-07-16T20:06:06+00:00" + "time": "2021-09-23T18:43:08+00:00" }, { "name": "amphp/byte-stream", @@ -620,16 +620,16 @@ }, { "name": "composer/semver", - "version": "3.2.5", + "version": "3.2.6", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + "reference": "83e511e247de329283478496f7a1e114c9517506" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", + "reference": "83e511e247de329283478496f7a1e114c9517506", "shasum": "" }, "require": { @@ -681,7 +681,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.5" + "source": "https://github.com/composer/semver/tree/3.2.6" }, "funding": [ { @@ -697,7 +697,7 @@ "type": "tidelift" } ], - "time": "2021-05-24T12:41:47+00:00" + "time": "2021-10-25T11:34:17+00:00" }, { "name": "composer/xdebug-handler", @@ -1081,16 +1081,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.12.0", + "version": "v4.13.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { @@ -1131,9 +1131,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { "name": "openlss/lib-array2xml", @@ -1354,16 +1354,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -1374,7 +1374,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -1404,22 +1405,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { @@ -1427,7 +1428,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -1453,39 +1455,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2021-10-02T14:08:47+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -1520,29 +1522,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", + "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.13.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1591,7 +1593,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" }, "funding": [ { @@ -1599,20 +1601,20 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-12-05T09:12:13+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -1651,7 +1653,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -1659,7 +1661,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -1844,16 +1846,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.8", + "version": "9.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb" + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/191768ccd5c85513b4068bdbe99bb6390c7d54fb", - "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", "shasum": "" }, "require": { @@ -1869,7 +1871,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-code-coverage": "^9.2.7", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -1931,7 +1933,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" }, "funding": [ { @@ -1943,26 +1945,31 @@ "type": "github" } ], - "time": "2021-07-31T15:17:34+00:00" + "time": "2021-09-25T07:38:51+00:00" }, { "name": "psr/container", - "version": "1.1.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -1989,9 +1996,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/log", @@ -2472,16 +2479,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", - "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", "shasum": "" }, "require": { @@ -2530,14 +2537,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" }, "funding": [ { @@ -2545,7 +2552,7 @@ "type": "github" } ], - "time": "2020-09-28T05:24:23+00:00" + "time": "2021-11-11T14:18:36+00:00" }, { "name": "sebastian/global-state", @@ -3009,26 +3016,26 @@ }, { "name": "symfony/console", - "version": "v5.3.6", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", + "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { "psr/log": ">=3", @@ -3043,12 +3050,12 @@ }, "require-dev": { "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3088,7 +3095,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.6" + "source": "https://github.com/symfony/console/tree/v5.4.1" }, "funding": [ { @@ -3104,29 +3111,29 @@ "type": "tidelift" } ], - "time": "2021-07-27T19:10:22+00:00" + "time": "2021-12-09T11:22:43+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -3155,7 +3162,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" }, "funding": [ { @@ -3171,7 +3178,7 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2021-11-01T23:48:49+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3578,21 +3585,24 @@ }, { "name": "symfony/service-contracts", - "version": "v2.4.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603", + "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1" + "php": ">=8.0.2", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "suggest": { "symfony/service-implementation": "" @@ -3600,7 +3610,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -3637,7 +3647,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.0.0" }, "funding": [ { @@ -3653,35 +3663,37 @@ "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2021-11-04T17:53:12+00:00" }, { "name": "symfony/string", - "version": "v5.3.3", + "version": "v6.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32", + "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -3720,7 +3732,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.3" + "source": "https://github.com/symfony/string/tree/v6.0.1" }, "funding": [ { @@ -3736,7 +3748,7 @@ "type": "tidelift" } ], - "time": "2021-06-27T11:44:38+00:00" + "time": "2021-12-08T15:13:44+00:00" }, { "name": "theseer/tokenizer", @@ -3948,16 +3960,16 @@ }, { "name": "webmozart/glob", - "version": "4.3.0", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/webmozarts/glob.git", - "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee" + "reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/glob/zipball/06358fafde0f32edb4513f4fd88fe113a40c90ee", - "reference": "06358fafde0f32edb4513f4fd88fe113a40c90ee", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/539b5dbc10021d3f9242e7a9e9b6b37843179e83", + "reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83", "shasum": "" }, "require": { @@ -3965,8 +3977,8 @@ "webmozart/path-util": "^2.2" }, "require-dev": { - "phpunit/phpunit": "^8.0", - "symfony/filesystem": "^5.1" + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.3" }, "type": "library", "extra": { @@ -3992,9 +4004,9 @@ "description": "A PHP implementation of Ant's glob.", "support": { "issues": "https://github.com/webmozarts/glob/issues", - "source": "https://github.com/webmozarts/glob/tree/4.3.0" + "source": "https://github.com/webmozarts/glob/tree/4.4.0" }, - "time": "2021-01-21T06:17:15+00:00" + "time": "2021-10-07T16:13:08+00:00" }, { "name": "webmozart/path-util", @@ -4044,18 +4056,28 @@ "issues": "https://github.com/webmozart/path-util/issues", "source": "https://github.com/webmozart/path-util/tree/2.3.0" }, + "abandoned": "symfony/filesystem", "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-database-and-namespace", + "alias": "0.13.0", + "alias_normalized": "0.13.0.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4", + "php": ">=8.0", "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } From 340774ba29828d0570ab9d91eadcd31e7a52d5d4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 23 Dec 2021 16:21:10 -0500 Subject: [PATCH 111/470] Create abuse collections inside default database --- src/Abuse/Adapters/TimeLimit.php | 2 +- tests/Abuse/AbuseTest.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 1db80ed6a76..2f8f90459ff 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -60,7 +60,7 @@ public function __construct(string $key, int $limit, int $time, Database $db) public function setup() : void { - if (!$this->db->exists()) { + if (!$this->db->exists($this->db->getDefaultDatabase())) { throw new Exception("You need to create database before running timelimit setup"); } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 2a80ea1a69e..84144ecf9a1 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -48,11 +48,12 @@ public function setUp(): void $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); + $db->setDefaultDatabase('utopiaTests'); $db->setNamespace('namespace'); $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - if(!$db->exists()) { - $db->create(); + if(!$db->exists('utopiaTests')) { + $db->create('utopiaTests'); $adapter->setup(); } From 52fb20e39e2e9619948bc0a73b52e10caa71350d Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 27 Dec 2021 14:06:45 +0100 Subject: [PATCH 112/470] fix: composer file --- composer.json | 2 +- composer.lock | 41 ++++++++++++++++------------------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index e6e130192dd..2c2239a16fa 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": "dev-feat-database-and-namespace as 0.13.0" + "utopia-php/database": ">=0.11 <1.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 112e23feb8a..323189f0fcf 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": "b29a70743916a44ea12f633cb28e4802", + "content-hash": "1b6651a4fddc462f63d3aaa50f6aa3ca", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-database-and-namespace", + "version": "0.13.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f4e02988c2dc4385c08dbb318c81462d5188ed8e" + "reference": "2e13987364f4966ec8a36784d4fb5df3a84e4e78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f4e02988c2dc4385c08dbb318c81462d5188ed8e", - "reference": "f4e02988c2dc4385c08dbb318c81462d5188ed8e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2e13987364f4966ec8a36784d4fb5df3a84e4e78", + "reference": "2e13987364f4966ec8a36784d4fb5df3a84e4e78", "shasum": "" }, "require": { @@ -397,9 +397,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-database-and-namespace" + "source": "https://github.com/utopia-php/database/tree/0.13.0" }, - "time": "2021-12-23T20:51:18+00:00" + "time": "2021-12-27T12:59:50+00:00" }, { "name": "utopia-php/framework", @@ -1846,16 +1846,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.10", + "version": "9.5.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + "reference": "2406855036db1102126125537adb1406f7242fdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2406855036db1102126125537adb1406f7242fdd", + "reference": "2406855036db1102126125537adb1406f7242fdd", "shasum": "" }, "require": { @@ -1933,11 +1933,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.11" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -1945,7 +1945,7 @@ "type": "github" } ], - "time": "2021-09-25T07:38:51+00:00" + "time": "2021-12-25T07:07:57+00:00" }, { "name": "psr/container", @@ -4060,18 +4060,9 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-database-and-namespace", - "alias": "0.13.0", - "alias_normalized": "0.13.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From eacfa57a9bc77616c638ee8ff5bcc9ac6ccd16d1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 18 Jul 2022 18:29:45 +0300 Subject: [PATCH 113/470] Unixtime to datetime changes --- README.md | 19 +- composer.json | 3 +- composer.lock | 523 ++++++++++++++++--------------- docker-compose.yml | 2 + src/Abuse/Abuse.php | 12 +- src/Abuse/Adapter.php | 15 +- src/Abuse/Adapters/ReCaptcha.php | 18 +- src/Abuse/Adapters/TimeLimit.php | 93 +++--- tests/Abuse/AbuseTest.php | 18 +- 9 files changed, 357 insertions(+), 346 deletions(-) diff --git a/README.md b/README.md index afc9e456a76..476c159d071 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,16 @@ use Utopia\Database\Database; $dbHost = '127.0.0.1'; $dbUser = 'travis'; $dbPass = ''; - -$pdo = new PDO("mysql:host={$dbHost};", $dbUser, $dbPass, array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - PDO::ATTR_TIMEOUT => 5, // Seconds -)); - -// Connection settings -$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays -$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$dbPort = '3306'; + +$pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, [ + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true, +]); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setNamespace('namespace'); diff --git a/composer.json b/composer.json index 2c2239a16fa..1276794a02e 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "dev-feat-attr-datetime as 0.18.7", + "ext-curl": "*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 323189f0fcf..fb019ad6592 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1b6651a4fddc462f63d3aaa50f6aa3ca", + "content-hash": "372f2a1d94e9c44faacc219797d1bf38", "packages": [ { "name": "composer/package-versions-deprecated", - "version": "1.11.99.4", + "version": "1.11.99.5", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b174585d1fe49ceed21928a945138948cb394600" + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", - "reference": "b174585d1fe49ceed21928a945138948cb394600", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", "shasum": "" }, "require": { @@ -61,7 +61,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" }, "funding": [ { @@ -77,7 +77,7 @@ "type": "tidelift" } ], - "time": "2021-09-13T08:41:34+00:00" + "time": "2022-01-17T14:14:24+00:00" }, { "name": "jean85/pretty-package-versions", @@ -167,12 +167,12 @@ } }, "autoload": { - "psr-4": { - "MongoDB\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "MongoDB\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -204,16 +204,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -222,7 +222,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -230,12 +230,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -267,7 +267,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" }, "funding": [ { @@ -283,26 +283,26 @@ "type": "tidelift" } ], - "time": "2021-07-28T13:41:28+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { "name": "utopia-php/cache", - "version": "0.4.2", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "7334c5b182c6ccf66b21ca61808d7c6c899e2bcb" + "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/7334c5b182c6ccf66b21ca61808d7c6c899e2bcb", - "reference": "7334c5b182c6ccf66b21ca61808d7c6c899e2bcb", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ea1353a4bbab617e23c865a7c97b60d8074aee3", + "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3", "shasum": "" }, "require": { "ext-json": "*", "ext-redis": "*", - "php": ">=7.4" + "php": ">=8.0" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -334,22 +334,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.4.2" + "source": "https://github.com/utopia-php/cache/tree/0.6.0" }, - "time": "2021-11-25T16:41:35+00:00" + "time": "2022-04-04T12:30:05+00:00" }, { "name": "utopia-php/database", - "version": "0.13.0", + "version": "dev-feat-attr-datetime", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2e13987364f4966ec8a36784d4fb5df3a84e4e78" + "reference": "c6f3671766127aa8c8bef853494a4059d3076b20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2e13987364f4966ec8a36784d4fb5df3a84e4e78", - "reference": "2e13987364f4966ec8a36784d4fb5df3a84e4e78", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c6f3671766127aa8c8bef853494a4059d3076b20", + "reference": "c6f3671766127aa8c8bef853494a4059d3076b20", "shasum": "" }, "require": { @@ -358,12 +358,13 @@ "ext-redis": "*", "mongodb/mongodb": "1.8.0", "php": ">=8.0", - "utopia-php/cache": "0.4.*", + "utopia-php/cache": "0.6.*", "utopia-php/framework": "0.*.*" }, "require-dev": { "fakerphp/faker": "^1.14", "phpunit/phpunit": "^9.4", + "swoole/ide-helper": "4.8.0", "utopia-php/cli": "^0.11.0", "vimeo/psalm": "4.0.1" }, @@ -397,22 +398,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.13.0" + "source": "https://github.com/utopia-php/database/tree/feat-attr-datetime" }, - "time": "2021-12-27T12:59:50+00:00" + "time": "2022-07-13T13:01:13+00:00" }, { "name": "utopia-php/framework", - "version": "0.19.3", + "version": "0.19.21", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7" + "reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/4c6c841d738cec458b73fec5aedd40fd43bd41a7", - "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/3b7bd8e4acf84fd7d560ced8e0142221d302575d", + "reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d", "shasum": "" }, "require": { @@ -446,24 +447,24 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.19.3" + "source": "https://github.com/utopia-php/framework/tree/0.19.21" }, - "time": "2021-12-17T13:04:13+00:00" + "time": "2022-05-12T18:42:28+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.1", + "version": "v2.6.2", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae" + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", - "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", "shasum": "" }, "require": { @@ -485,13 +486,13 @@ } }, "autoload": { - "psr-4": { - "Amp\\": "lib" - }, "files": [ "lib/functions.php", "lib/Internal/functions.php" - ] + ], + "psr-4": { + "Amp\\": "lib" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -516,7 +517,7 @@ } ], "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "http://amphp.org/amp", + "homepage": "https://amphp.org/amp", "keywords": [ "async", "asynchronous", @@ -531,7 +532,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.1" + "source": "https://github.com/amphp/amp/tree/v2.6.2" }, "funding": [ { @@ -539,7 +540,7 @@ "type": "github" } ], - "time": "2021-09-23T18:43:08+00:00" + "time": "2022-02-20T17:52:18+00:00" }, { "name": "amphp/byte-stream", @@ -574,12 +575,12 @@ } }, "autoload": { - "psr-4": { - "Amp\\ByteStream\\": "lib" - }, "files": [ "lib/functions.php" - ] + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -620,23 +621,23 @@ }, { "name": "composer/semver", - "version": "3.2.6", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "83e511e247de329283478496f7a1e114c9517506" + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", - "reference": "83e511e247de329283478496f7a1e114c9517506", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", + "phpstan/phpstan": "^1.4", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", @@ -681,7 +682,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.6" + "source": "https://github.com/composer/semver/tree/3.3.2" }, "funding": [ { @@ -697,7 +698,7 @@ "type": "tidelift" } ], - "time": "2021-10-25T11:34:17+00:00" + "time": "2022-04-01T19:23:25+00:00" }, { "name": "composer/xdebug-handler", @@ -802,29 +803,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -851,7 +853,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -867,7 +869,7 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -916,16 +918,16 @@ }, { "name": "felixfbecker/language-server-protocol", - "version": "1.5.1", + "version": "v1.5.2", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", - "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", "shasum": "" }, "require": { @@ -966,43 +968,44 @@ ], "support": { "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" }, - "time": "2021-02-22T14:02:09+00:00" + "time": "2022-03-02T22:36:06+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1018,7 +1021,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -1026,7 +1029,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "netresearch/jsonmapper", @@ -1081,16 +1084,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.13.2", + "version": "v4.14.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -1131,9 +1134,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" }, - "time": "2021-11-30T19:35:32+00:00" + "time": "2022-05-31T20:59:12+00:00" }, { "name": "openlss/lib-array2xml", @@ -1250,16 +1253,16 @@ }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -1295,9 +1298,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1411,16 +1414,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.5.1", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { @@ -1455,9 +1458,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2021-10-02T14:08:47+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", @@ -1528,16 +1531,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.10", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { @@ -1593,7 +1596,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -1601,7 +1604,7 @@ "type": "github" } ], - "time": "2021-12-05T09:12:13+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1846,16 +1849,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.11", + "version": "9.5.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2406855036db1102126125537adb1406f7242fdd" + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2406855036db1102126125537adb1406f7242fdd", - "reference": "2406855036db1102126125537adb1406f7242fdd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", "shasum": "" }, "require": { @@ -1871,7 +1874,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -1885,11 +1888,10 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { - "ext-pdo": "*", "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { @@ -1906,11 +1908,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1933,7 +1935,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" }, "funding": [ { @@ -1945,7 +1947,7 @@ "type": "github" } ], - "time": "2021-12-25T07:07:57+00:00" + "time": "2022-06-19T12:14:25+00:00" }, { "name": "psr/container", @@ -2416,16 +2418,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -2467,7 +2469,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -2475,7 +2477,7 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", @@ -2556,16 +2558,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -2608,7 +2610,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -2616,7 +2618,7 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -2907,28 +2909,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2951,7 +2953,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -2959,7 +2961,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -3016,16 +3018,16 @@ }, { "name": "symfony/console", - "version": "v5.4.1", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4" + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", - "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", + "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000", "shasum": "" }, "require": { @@ -3095,7 +3097,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.1" + "source": "https://github.com/symfony/console/tree/v5.4.10" }, "funding": [ { @@ -3111,29 +3113,29 @@ "type": "tidelift" } ], - "time": "2021-12-09T11:22:43+00:00" + "time": "2022-06-26T13:00:04+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -3162,7 +3164,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" }, "funding": [ { @@ -3178,32 +3180,35 @@ "type": "tidelift" } ], - "time": "2021-11-01T23:48:49+00:00" + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3211,12 +3216,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3241,7 +3246,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -3257,20 +3262,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -3282,7 +3287,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3290,12 +3295,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3322,7 +3327,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -3338,20 +3343,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -3363,7 +3368,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3371,12 +3376,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3406,7 +3411,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -3422,32 +3427,35 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.1", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3455,12 +3463,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3486,7 +3494,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -3502,20 +3510,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { @@ -3524,7 +3532,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3532,12 +3540,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3565,7 +3573,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" }, "funding": [ { @@ -3581,24 +3589,24 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.0.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603" + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/container": "^2.0" }, "conflict": { @@ -3610,7 +3618,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -3620,7 +3628,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3647,7 +3658,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" }, "funding": [ { @@ -3663,24 +3674,24 @@ "type": "tidelift" } ], - "time": "2021-11-04T17:53:12+00:00" + "time": "2022-05-30T19:18:58+00:00" }, { "name": "symfony/string", - "version": "v6.0.1", + "version": "v6.1.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32" + "reference": "1903f2879875280c5af944625e8246d81c2f0604" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32", - "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32", + "url": "https://api.github.com/repos/symfony/string/zipball/1903f2879875280c5af944625e8246d81c2f0604", + "reference": "1903f2879875280c5af944625e8246d81c2f0604", "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -3697,12 +3708,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -3732,7 +3743,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.1" + "source": "https://github.com/symfony/string/tree/v6.1.2" }, "funding": [ { @@ -3748,7 +3759,7 @@ "type": "tidelift" } ], - "time": "2021-12-08T15:13:44+00:00" + "time": "2022-06-26T16:35:04+00:00" }, { "name": "theseer/tokenizer", @@ -3876,13 +3887,13 @@ } }, "autoload": { - "psr-4": { - "Psalm\\": "src/Psalm/" - }, "files": [ "src/functions.php", "src/spl_object_id.php" - ] + ], + "psr-4": { + "Psalm\\": "src/Psalm/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3960,21 +3971,20 @@ }, { "name": "webmozart/glob", - "version": "4.4.0", + "version": "4.6.0", "source": { "type": "git", "url": "https://github.com/webmozarts/glob.git", - "reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83" + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/glob/zipball/539b5dbc10021d3f9242e7a9e9b6b37843179e83", - "reference": "539b5dbc10021d3f9242e7a9e9b6b37843179e83", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0.0", - "webmozart/path-util": "^2.2" + "php": "^7.3 || ^8.0.0" }, "require-dev": { "phpunit/phpunit": "^9.5", @@ -4004,9 +4014,9 @@ "description": "A PHP implementation of Ant's glob.", "support": { "issues": "https://github.com/webmozarts/glob/issues", - "source": "https://github.com/webmozarts/glob/tree/4.4.0" + "source": "https://github.com/webmozarts/glob/tree/4.6.0" }, - "time": "2021-10-07T16:13:08+00:00" + "time": "2022-05-24T19:45:58+00:00" }, { "name": "webmozart/path-util", @@ -4060,9 +4070,18 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-attr-datetime", + "alias": "0.18.7", + "alias_normalized": "0.18.7.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4070,5 +4089,5 @@ "ext-pdo": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 06c0d854b67..034d2b704bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: - MYSQL_ROOT_PASSWORD=password networks: - abuse + ports: + - "9307:3306" php8: build: diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index ea263e7e699..f12e8565740 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -7,7 +7,7 @@ class Abuse /** * @var Adapter */ - protected $adapter; + protected Adapter $adapter; /** * @param Adapter $adapter @@ -45,14 +45,14 @@ public function getLogs(int $offset, int $limit): array } /** - * Delete all logs older than $timestamp seconds + * Delete all logs older than $datetime + * + * @param string $datetime * - * @param int $timestamp - * * @return bool */ - public function cleanup(int $timestamp): bool + public function cleanup(string $datetime): bool { - return $this->adapter->cleanup($timestamp); + return $this->adapter->cleanup($datetime); } } \ No newline at end of file diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 3a38289cb7b..90cf1974c59 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -11,27 +11,26 @@ interface Adapter * * @return bool */ - public function check(); + public function check(): bool; /** * Get abuse logs * * Returns logs with an offset and limit * - * @param $offset - * @param $limit - * + * @param int $offset + * @param int $limit + * * @return array */ public function getLogs(int $offset, int $limit): array; /** - * Delete all logs older than $timestamp seconds + * Delete all logs older than $datetime * - * @param int $timestamp - * + * @param string $datetime * @return bool */ - public function cleanup(int $timestamp): bool; + public function cleanup(string $datetime): bool; } \ No newline at end of file diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index b9887d92496..7ae0a85e322 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -88,14 +88,13 @@ public function check($score = 0.5):bool } /** - * Delete logs older than $timestamp seconds - * - * @param int $timestamp - * + * Delete logs older than $datetime + * + * @param string $datetime + * @return bool * @throws Exception - * @return bool */ - public function cleanup(int $timestamp):bool + public function cleanup(string $datetime):bool { throw new Exception('Method not supported'); } @@ -105,10 +104,11 @@ public function cleanup(int $timestamp):bool * * Returns logs with an offset and limit * - * @param $offset - * @param $limit - * + * @param int $offset + * @param int $limit + * * @return array + * @throws Exception */ public function getLogs(int $offset, int $limit): array { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 2f8f90459ff..6daee718506 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -2,9 +2,9 @@ namespace Utopia\Abuse\Adapters; -use PDO; use Utopia\Abuse\Adapter; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -17,43 +17,44 @@ class TimeLimit implements Adapter /** * @var Database */ - protected $db; + protected Database $db; /** * @var string */ - protected $key = ''; + protected string $key = ''; /** - * @var int + * @var string */ - protected $time = 0; + protected string $time; /** * @var int */ - protected $limit = 0; + protected int $limit = 0; /** * @var int|null */ - protected $count = null; + protected ?int $count = null; /** * @var array */ - protected $params = array(); + protected array $params = []; /** * @param string $key - * @param int $time + * @param int $seconds * @param int $limit * @param Database $db */ - public function __construct(string $key, int $limit, int $time, Database $db) + public function __construct(string $key, int $limit, int $seconds, Database $db) { $this->key = $key; - $this->time = (int) \date('U', (int) (\floor(\time() / $time)) * $time); + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); $this->limit = $limit; $this->db = $db; } @@ -76,7 +77,7 @@ public function setup() : void ]), new Document([ '$id' => 'time', - 'type' => Database::VAR_INTEGER, + 'type' => Database::VAR_DATETIME, 'size' => 0, 'required' => true, 'signed' => false, @@ -102,13 +103,6 @@ public function setup() : void 'lengths' => [], 'orders' => [], ]), - new Document([ - '$id' => 'index1', - 'type' => Database::INDEX_KEY, - 'attributes' => ['key', 'time'], - 'lengths' => [], - 'orders' => [], - ]), new Document([ '$id' => 'index2', 'type' => Database::INDEX_KEY, @@ -170,11 +164,11 @@ protected function parseKey(): string * Checks if number of counts is bigger or smaller than current limit * * @param string $key - * @param int $time + * @param string $datetime * @return int - * @throws Exception + * @throws \Exception */ - protected function count(string $key, int $time): int + protected function count(string $key, string $datetime): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -187,8 +181,8 @@ protected function count(string $key, int $time): int Authorization::disable(); $result = $this->db->find(TimeLimit::COLLECTION, [ new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$time]), - ], 1, 0, ['_id'], ['DESC']); + new Query('time', Query::TYPE_EQUAL, [$datetime]), + ]); Authorization::reset(); if (\count($result) === 1) { @@ -204,42 +198,36 @@ protected function count(string $key, int $time): int /** * @param string $key - * @param int $time seconds - * - * @throws Exception + * @param string $datetime * * @return void */ - protected function hit(string $key, int $time): void + protected function hit(string $key, string $datetime): void { if (0 == $this->limit) { // No limit no point for counting return; } Authorization::disable(); - + $existing = $this->db->find(TimeLimit::COLLECTION, [ new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$time]), - ], 1, 0, ['_id'], ['DESC']); - - $data = [ - '$read' => [], - '$write' => [], - 'key' => $key, - 'time' => $time, - 'count' => 1, - '$collection' => TimeLimit::COLLECTION, - ]; + new Query('time', Query::TYPE_EQUAL, [$datetime]), + ]); if (\count($existing) === 1) { - //update - $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), new Document(array_merge($data, [ - 'count' => $existing[0]->getAttribute('count',0) + 1, - '$id' => $existing[0]->getId(), - ]))); + $data = $existing[0]; + $data->setAttribute('count', $existing[0]->getAttribute('count',0) + 1); + $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), $data); } else { - //create + $data = [ + '$read' => [], + '$write' => [], + 'key' => $key, + 'time' => $datetime, + 'count' => 1, + '$collection' => TimeLimit::COLLECTION, + ]; $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } @@ -270,17 +258,18 @@ public function getLogs(int $offset, int $limit): array /** * Delete logs older than $timestamp seconds * - * @param int $timestamp + * @param string $datetime * * @return bool + * @throws \Utopia\Database\Exception\Authorization */ - public function cleanup(int $timestamp): bool + public function cleanup(string $datetime): bool { Authorization::disable(); do { $documents = $this->db->find(TimeLimit::COLLECTION, [ - new Query('time', Query::TYPE_LESSER, [$timestamp]), + new Query('time', Query::TYPE_LESSER, [$datetime]), ]); foreach ($documents as $document) { @@ -347,11 +336,11 @@ public function limit(): int /** * Time * - * Return the unix time + * Return the Datetime * - * @return int + * @return string */ - public function time(): int + public function time(): string { return $this->time; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 84144ecf9a1..a672a9bc5f9 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -21,6 +21,7 @@ use Utopia\Cache\Cache; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; +use Utopia\Database\DateTime; class AbuseTest extends TestCase { @@ -37,15 +38,14 @@ public function setUp(): void $dbPort = '3306'; $dbPass = 'password'; - $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, [ PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true - )); - - // Connection settings - $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); // Return arrays - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true, + ]); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDefaultDatabase('utopiaTests'); @@ -85,7 +85,7 @@ public function testCleanup() sleep(5); // Delete the log - $status = $this->abuse->cleanup(time() - 1); + $status = $this->abuse->cleanup(DateTime::addSeconds(new \DateTime(), -1)); $this->assertEquals($status, true); // Check that there are no logs in the DB From d5f121650544f8d97bb1ea6a58cb5ee4584d2c62 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 19 Jul 2022 10:22:15 +0200 Subject: [PATCH 114/470] ci: fix travis os --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d870ccd0a96..08f1548cc71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: xenial +dist: focal arch: - amd64 From cc811b14d550d389b134d897ecef06e8d278db53 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 19 Jul 2022 13:14:58 +0300 Subject: [PATCH 115/470] MariaDB::getPdoAttributes --- composer.lock | 13 +++++++------ tests/Abuse/AbuseTest.php | 10 ++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index fb019ad6592..e8513996f4b 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": "372f2a1d94e9c44faacc219797d1bf38", + "content-hash": "9e5cae3746a2d97fa9eafa58568a1b7f", "packages": [ { "name": "composer/package-versions-deprecated", @@ -344,12 +344,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c6f3671766127aa8c8bef853494a4059d3076b20" + "reference": "407c0e95a4f5cf9b8932dc5b314a93b634339c09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c6f3671766127aa8c8bef853494a4059d3076b20", - "reference": "c6f3671766127aa8c8bef853494a4059d3076b20", + "url": "https://api.github.com/repos/utopia-php/database/zipball/407c0e95a4f5cf9b8932dc5b314a93b634339c09", + "reference": "407c0e95a4f5cf9b8932dc5b314a93b634339c09", "shasum": "" }, "require": { @@ -400,7 +400,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-attr-datetime" }, - "time": "2022-07-13T13:01:13+00:00" + "time": "2022-07-19T10:06:50+00:00" }, { "name": "utopia-php/framework", @@ -4086,7 +4086,8 @@ "prefer-lowest": false, "platform": { "php": ">=8.0", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-curl": "*" }, "platform-dev": [], "plugin-api-version": "2.2.0" diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index a672a9bc5f9..763675bb3cb 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -19,6 +19,7 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; +use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -38,14 +39,7 @@ public function setUp(): void $dbPort = '3306'; $dbPass = 'password'; - $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, [ - PDO::ATTR_TIMEOUT => 3, // Seconds - PDO::ATTR_PERSISTENT => true, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_EMULATE_PREPARES => true, - PDO::ATTR_STRINGIFY_FETCHES => true, - ]); + $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPdoAttributes()); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDefaultDatabase('utopiaTests'); From 8fc98103195ad014a66388e0986da78fbd65a8fd Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 19 Jul 2022 13:31:15 +0300 Subject: [PATCH 116/470] findOne --- src/Abuse/Adapters/TimeLimit.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 6daee718506..acfe8545f34 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -210,16 +210,12 @@ protected function hit(string $key, string $datetime): void Authorization::disable(); - $existing = $this->db->find(TimeLimit::COLLECTION, [ + $data = $this->db->findOne(TimeLimit::COLLECTION, [ new Query('key', Query::TYPE_EQUAL, [$key]), new Query('time', Query::TYPE_EQUAL, [$datetime]), ]); - if (\count($existing) === 1) { - $data = $existing[0]; - $data->setAttribute('count', $existing[0]->getAttribute('count',0) + 1); - $this->db->updateDocument(TimeLimit::COLLECTION, $existing[0]->getId(), $data); - } else { + if ($data === false) { $data = [ '$read' => [], '$write' => [], @@ -229,6 +225,9 @@ protected function hit(string $key, string $datetime): void '$collection' => TimeLimit::COLLECTION, ]; $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + } else { + $data->setAttribute('count', $data->getAttribute('count',0) + 1); + $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); } Authorization::reset(); From 24fea23637e08267c2f35ae7704b249566d8333f Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 21 Jul 2022 11:08:35 +0300 Subject: [PATCH 117/470] abuse --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index e8513996f4b..bbb268beecc 100644 --- a/composer.lock +++ b/composer.lock @@ -344,12 +344,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "407c0e95a4f5cf9b8932dc5b314a93b634339c09" + "reference": "6bcbf8d5a55f9da6d955f6428eff4724d04f60d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/407c0e95a4f5cf9b8932dc5b314a93b634339c09", - "reference": "407c0e95a4f5cf9b8932dc5b314a93b634339c09", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6bcbf8d5a55f9da6d955f6428eff4724d04f60d8", + "reference": "6bcbf8d5a55f9da6d955f6428eff4724d04f60d8", "shasum": "" }, "require": { @@ -400,7 +400,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-attr-datetime" }, - "time": "2022-07-19T10:06:50+00:00" + "time": "2022-07-20T17:14:33+00:00" }, { "name": "utopia-php/framework", From 48b93d69550b6f99e9f3f1f9c8348406d89aa6c0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 2 Aug 2022 19:41:45 +0300 Subject: [PATCH 118/470] utopia-php/database tag change back --- composer.json | 3 +-- composer.lock | 66 ++++++++++++++++++++++----------------------------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index 1276794a02e..2c2239a16fa 100755 --- a/composer.json +++ b/composer.json @@ -17,8 +17,7 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": "dev-feat-attr-datetime as 0.18.7", - "ext-curl": "*" + "utopia-php/database": ">=0.11 <1.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index bbb268beecc..454114a2333 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": "9e5cae3746a2d97fa9eafa58568a1b7f", + "content-hash": "1b6651a4fddc462f63d3aaa50f6aa3ca", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-attr-datetime", + "version": "0.19.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6bcbf8d5a55f9da6d955f6428eff4724d04f60d8" + "reference": "207d9f2665bf2124797351dfdcc928a46a301b04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6bcbf8d5a55f9da6d955f6428eff4724d04f60d8", - "reference": "6bcbf8d5a55f9da6d955f6428eff4724d04f60d8", + "url": "https://api.github.com/repos/utopia-php/database/zipball/207d9f2665bf2124797351dfdcc928a46a301b04", + "reference": "207d9f2665bf2124797351dfdcc928a46a301b04", "shasum": "" }, "require": { @@ -398,22 +398,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-attr-datetime" + "source": "https://github.com/utopia-php/database/tree/0.19.0" }, - "time": "2022-07-20T17:14:33+00:00" + "time": "2022-08-01T11:51:01+00:00" }, { "name": "utopia-php/framework", - "version": "0.19.21", + "version": "0.20.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d" + "reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/3b7bd8e4acf84fd7d560ced8e0142221d302575d", - "reference": "3b7bd8e4acf84fd7d560ced8e0142221d302575d", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/beb5e861c7d0a6256a1272e6b9d70b060ca8629a", + "reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a", "shasum": "" }, "require": { @@ -447,9 +447,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.19.21" + "source": "https://github.com/utopia-php/framework/tree/0.20.0" }, - "time": "2022-05-12T18:42:28+00:00" + "time": "2022-07-30T09:55:28+00:00" } ], "packages-dev": [ @@ -3018,16 +3018,16 @@ }, { "name": "symfony/console", - "version": "v5.4.10", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000" + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000", - "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", "shasum": "" }, "require": { @@ -3097,7 +3097,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.10" + "source": "https://github.com/symfony/console/tree/v5.4.11" }, "funding": [ { @@ -3113,7 +3113,7 @@ "type": "tidelift" } ], - "time": "2022-06-26T13:00:04+00:00" + "time": "2022-07-22T10:42:43+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3678,16 +3678,16 @@ }, { "name": "symfony/string", - "version": "v6.1.2", + "version": "v6.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "1903f2879875280c5af944625e8246d81c2f0604" + "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/1903f2879875280c5af944625e8246d81c2f0604", - "reference": "1903f2879875280c5af944625e8246d81c2f0604", + "url": "https://api.github.com/repos/symfony/string/zipball/f35241f45c30bcd9046af2bb200a7086f70e1d6b", + "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b", "shasum": "" }, "require": { @@ -3743,7 +3743,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.2" + "source": "https://github.com/symfony/string/tree/v6.1.3" }, "funding": [ { @@ -3759,7 +3759,7 @@ "type": "tidelift" } ], - "time": "2022-06-26T16:35:04+00:00" + "time": "2022-07-27T15:50:51+00:00" }, { "name": "theseer/tokenizer", @@ -4070,24 +4070,14 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-attr-datetime", - "alias": "0.18.7", - "alias_normalized": "0.18.7.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.0", - "ext-pdo": "*", - "ext-curl": "*" + "ext-pdo": "*" }, "platform-dev": [], "plugin-api-version": "2.2.0" From 57b5454b37bba9ebf36fc7cbba49786e92a3a577 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 10 Aug 2022 16:50:31 +1200 Subject: [PATCH 119/470] Update permissions --- composer.json | 2 +- composer.lock | 23 ++++++++++-------- src/Abuse/Adapters/TimeLimit.php | 41 +++++++++++++++----------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 2c2239a16fa..f251a1e56fc 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "dev-refactor-permissions" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 454114a2333..0edc92121d5 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": "1b6651a4fddc462f63d3aaa50f6aa3ca", + "content-hash": "fb1ac7b869309cf3b3961911571e09a2", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.19.0", + "version": "dev-refactor-permissions", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "207d9f2665bf2124797351dfdcc928a46a301b04" + "reference": "2e33ddd74c9950abdacabd5f0745564674c6d74f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/207d9f2665bf2124797351dfdcc928a46a301b04", - "reference": "207d9f2665bf2124797351dfdcc928a46a301b04", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2e33ddd74c9950abdacabd5f0745564674c6d74f", + "reference": "2e33ddd74c9950abdacabd5f0745564674c6d74f", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.19.0" + "source": "https://github.com/utopia-php/database/tree/refactor-permissions" }, - "time": "2022-08-01T11:51:01+00:00" + "time": "2022-08-09T00:27:04+00:00" }, { "name": "utopia-php/framework", @@ -4072,13 +4072,16 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.0", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index acfe8545f34..e053df06988 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -208,29 +208,26 @@ protected function hit(string $key, string $datetime): void return; } - Authorization::disable(); - - $data = $this->db->findOne(TimeLimit::COLLECTION, [ - new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$datetime]), - ]); + Authorization::skip(function () use ($datetime, $key) { + $data = $this->db->findOne(TimeLimit::COLLECTION, [ + new Query('key', Query::TYPE_EQUAL, [$key]), + new Query('time', Query::TYPE_EQUAL, [$datetime]), + ]); - if ($data === false) { - $data = [ - '$read' => [], - '$write' => [], - 'key' => $key, - 'time' => $datetime, - 'count' => 1, - '$collection' => TimeLimit::COLLECTION, - ]; - $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); - } else { - $data->setAttribute('count', $data->getAttribute('count',0) + 1); - $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); - } - - Authorization::reset(); + if ($data === false) { + $data = [ + '$permissions' => [], + 'key' => $key, + 'time' => $datetime, + 'count' => 1, + '$collection' => TimeLimit::COLLECTION, + ]; + $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + } else { + $data->setAttribute('count', $data->getAttribute('count', 0) + 1); + $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); + } + }); $this->count++; } From b389f73d793137baeef6e7621a8ea4a75dc5cb13 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 10 Aug 2022 16:51:49 +1200 Subject: [PATCH 120/470] Use skip instead of disable/reset for auth --- composer.json | 1 + src/Abuse/Abuse.php | 10 ++-- src/Abuse/Adapter.php | 2 +- src/Abuse/Adapters/ReCaptcha.php | 12 ++--- src/Abuse/Adapters/TimeLimit.php | 78 ++++++++++++++++++-------------- 5 files changed, 56 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index 2c2239a16fa..784af31e66b 100755 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "utopia-php/database": ">=0.11 <1.0" + "ext-curl": "*", }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index f12e8565740..10565870942 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -24,7 +24,7 @@ public function __construct(Adapter $adapter) * * @return bool */ - public function check() + public function check(): bool { return $this->adapter->check(); } @@ -32,11 +32,11 @@ public function check() /** * Get abuse logs * - * Returns logs with an offset and limit + * Return logs with an offset and limit + * + * @param int $offset + * @param int $limit * - * @param $offset - * @param $limit - * * @return array */ public function getLogs(int $offset, int $limit): array diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 90cf1974c59..9ea49d1a7af 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -16,7 +16,7 @@ public function check(): bool; /** * Get abuse logs * - * Returns logs with an offset and limit + * Return logs with an offset and limit * * @param int $offset * @param int $limit diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 7ae0a85e322..8f2e4340ede 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -13,21 +13,21 @@ class ReCaptcha implements Adapter * * @var string */ - protected $secret = ''; + protected string $secret = ''; /** * The value of 'g-recaptcha-response'. * * @var string */ - protected $response = ''; + protected string $response = ''; /** * The end user's ip address * * @var string */ - protected $remoteIP = ''; + protected string $remoteIP = ''; /** * ReCaptcha Adapter @@ -42,7 +42,7 @@ class ReCaptcha implements Adapter * @param string $response * @param string $remoteIP */ - public function __construct($secret, $response, $remoteIP) + public function __construct(string $secret, string $response, string $remoteIP) { $this->secret = $secret; $this->response = $response; @@ -57,7 +57,7 @@ public function __construct($secret, $response, $remoteIP) * @param float $score * @return bool */ - public function check($score = 0.5):bool + public function check(float $score = 0.5):bool { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array( @@ -102,7 +102,7 @@ public function cleanup(string $datetime):bool /** * Get abuse logs * - * Returns logs with an offset and limit + * Return logs with an offset and limit * * @param int $offset * @param int $limit diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index acfe8545f34..f2e7daa4ab4 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -6,6 +6,10 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception\Authorization as AuthorizationException; +use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Limit; +use Utopia\Database\Exception\Structure; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Exception; @@ -53,13 +57,18 @@ class TimeLimit implements Adapter public function __construct(string $key, int $limit, int $seconds, Database $db) { $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $time = (int)\date('U', (int)(\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); $this->limit = $limit; $this->db = $db; } - public function setup() : void + /** + * @throws Limit + * @throws Duplicate + * @throws Exception|\Exception + */ + public function setup(): void { if (!$this->db->exists($this->db->getDefaultDatabase())) { throw new Exception("You need to create database before running timelimit setup"); @@ -178,20 +187,20 @@ protected function count(string $key, string $datetime): int return $this->count; } - Authorization::disable(); - $result = $this->db->find(TimeLimit::COLLECTION, [ - new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$datetime]), - ]); - Authorization::reset(); + $result = Authorization::skip(function () use ($key, $datetime) { + return $this->db->find(TimeLimit::COLLECTION, [ + new Query('key', Query::TYPE_EQUAL, [$key]), + new Query('time', Query::TYPE_EQUAL, [$datetime]), + ]); + }); if (\count($result) === 1) { - $result = $result[0]->getAttribute('count',0); + $result = $result[0]->getAttribute('count', 0); } else { $result = 0; } - $this->count = (int) $result; + $this->count = (int)$result; return $this->count; } @@ -201,6 +210,7 @@ protected function count(string $key, string $datetime): int * @param string $datetime * * @return void + * @throws AuthorizationException|Structure|\Exception */ protected function hit(string $key, string $datetime): void { @@ -238,20 +248,19 @@ protected function hit(string $key, string $datetime): void /** * Get abuse logs * - * Returns logs with an offset and limit + * Return logs with an offset and limit * - * @param $offset - * @param $limit + * @param int $offset + * @param int $limit * * @return array + * @throws \Exception */ public function getLogs(int $offset, int $limit): array { - Authorization::disable(); - $result = $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset, ['_id'], ['DESC']); - Authorization::reset(); - - return $result; + return Authorization::skip(function () use ($offset, $limit) { + return $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset, ['_id'], ['DESC']); + }); } /** @@ -260,23 +269,22 @@ public function getLogs(int $offset, int $limit): array * @param string $datetime * * @return bool - * @throws \Utopia\Database\Exception\Authorization + * @throws AuthorizationException|\Exception */ public function cleanup(string $datetime): bool { - Authorization::disable(); - - do { - $documents = $this->db->find(TimeLimit::COLLECTION, [ - new Query('time', Query::TYPE_LESSER, [$datetime]), - ]); - - foreach ($documents as $document) { - $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); - } - } while(!empty($documents)); - - Authorization::reset(); + Authorization::skip(function () use ($datetime) { + do { + $documents = $this->db->find(TimeLimit::COLLECTION, [ + new Query('time', Query::TYPE_LESSER, [$datetime]), + ]); + + foreach ($documents as $document) { + $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); + } + } while (!empty($documents)); + + }); return true; } @@ -287,7 +295,7 @@ public function cleanup(string $datetime): bool * Checks if number of counts is bigger or smaller than current limit. limit 0 is equal to unlimited * * @return bool - * @throws Exception + * @throws \Exception */ public function check(): bool { @@ -310,9 +318,9 @@ public function check(): bool * * Returns the number of current remaining counts * - * @throws Exception - * * @return int + * @throws \Exception + * */ public function remaining(): int { From 407b812403acb320dfd4022d41e3b194374ead6a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 10 Aug 2022 16:56:28 +1200 Subject: [PATCH 121/470] Auth skip --- src/Abuse/Adapters/TimeLimit.php | 44 +++++++++++++++----------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f2e7daa4ab4..a4e87e4936e 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -218,29 +218,27 @@ protected function hit(string $key, string $datetime): void return; } - Authorization::disable(); - - $data = $this->db->findOne(TimeLimit::COLLECTION, [ - new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$datetime]), - ]); - - if ($data === false) { - $data = [ - '$read' => [], - '$write' => [], - 'key' => $key, - 'time' => $datetime, - 'count' => 1, - '$collection' => TimeLimit::COLLECTION, - ]; - $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); - } else { - $data->setAttribute('count', $data->getAttribute('count',0) + 1); - $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); - } - - Authorization::reset(); + Authorization::skip(function () use ($key, $datetime) { + $data = $this->db->findOne(TimeLimit::COLLECTION, [ + new Query('key', Query::TYPE_EQUAL, [$key]), + new Query('time', Query::TYPE_EQUAL, [$datetime]), + ]); + + if ($data === false) { + $data = [ + '$read' => [], + '$write' => [], + 'key' => $key, + 'time' => $datetime, + 'count' => 1, + '$collection' => TimeLimit::COLLECTION, + ]; + $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + } else { + $data->setAttribute('count', $data->getAttribute('count', 0) + 1); + $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); + } + }); $this->count++; } From 07518cea07ccce4325e9b5c224d994f6fe22dc3b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 10 Aug 2022 16:57:39 +1200 Subject: [PATCH 122/470] Fix composer --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 784af31e66b..7c9931c6de9 100755 --- a/composer.json +++ b/composer.json @@ -17,8 +17,8 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": ">=0.11 <1.0" "ext-curl": "*", + "utopia-php/database": ">=0.11 <1.0" }, "require-dev": { "phpunit/phpunit": "^9.4", From 5c200defd69155c956fc127e8e1771ef820f9dad Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 10 Aug 2022 19:51:33 +0000 Subject: [PATCH 123/470] Update database query syntax --- composer.json | 8 ++++++- composer.lock | 40 ++++++++++++++------------------ src/Abuse/Adapters/TimeLimit.php | 26 ++++++++++----------- 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/composer.json b/composer.json index 2c2239a16fa..c0fa28126ed 100755 --- a/composer.json +++ b/composer.json @@ -17,8 +17,14 @@ "require": { "php": ">=8.0", "ext-pdo": "*", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "dev-feat-query-gen-2.3" }, + "repositories": [ + { + "url": "https://github.com/utopia-php/database.git", + "type": "git" + } + ], "require-dev": { "phpunit/phpunit": "^9.4", "vimeo/psalm": "4.0.1" diff --git a/composer.lock b/composer.lock index 454114a2333..27cd02f9bc3 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": "1b6651a4fddc462f63d3aaa50f6aa3ca", + "content-hash": "e828cdee7b40b943fd9ab65dff7006e8", "packages": [ { "name": "composer/package-versions-deprecated", @@ -287,16 +287,16 @@ }, { "name": "utopia-php/cache", - "version": "0.6.0", + "version": "0.6.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3" + "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ea1353a4bbab617e23c865a7c97b60d8074aee3", - "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/9889235a6d3da6cbb1f435201529da4d27c30e79", + "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79", "shasum": "" }, "require": { @@ -334,23 +334,17 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.6.0" + "source": "https://github.com/utopia-php/cache/tree/0.6.1" }, - "time": "2022-04-04T12:30:05+00:00" + "time": "2022-08-10T08:12:46+00:00" }, { "name": "utopia-php/database", - "version": "0.19.0", + "version": "dev-feat-query-gen-2.3", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "207d9f2665bf2124797351dfdcc928a46a301b04" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/207d9f2665bf2124797351dfdcc928a46a301b04", - "reference": "207d9f2665bf2124797351dfdcc928a46a301b04", - "shasum": "" + "reference": "39e3e894dde8607e61bae5e1e5f294dc614f3053" }, "require": { "ext-mongodb": "*", @@ -374,7 +368,11 @@ "Utopia\\Database\\": "src/Database" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Database" + } + }, "license": [ "MIT" ], @@ -396,11 +394,7 @@ "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.19.0" - }, - "time": "2022-08-01T11:51:01+00:00" + "time": "2022-08-09T22:41:16+00:00" }, { "name": "utopia-php/framework", @@ -4072,7 +4066,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index acfe8545f34..bbbf6b8f1eb 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -59,7 +59,7 @@ public function __construct(string $key, int $limit, int $seconds, Database $db) $this->db = $db; } - public function setup() : void + public function setup(): void { if (!$this->db->exists($this->db->getDefaultDatabase())) { throw new Exception("You need to create database before running timelimit setup"); @@ -180,13 +180,13 @@ protected function count(string $key, string $datetime): int Authorization::disable(); $result = $this->db->find(TimeLimit::COLLECTION, [ - new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$datetime]), + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), ]); Authorization::reset(); if (\count($result) === 1) { - $result = $result[0]->getAttribute('count',0); + $result = $result[0]->getAttribute('count', 0); } else { $result = 0; } @@ -211,8 +211,8 @@ protected function hit(string $key, string $datetime): void Authorization::disable(); $data = $this->db->findOne(TimeLimit::COLLECTION, [ - new Query('key', Query::TYPE_EQUAL, [$key]), - new Query('time', Query::TYPE_EQUAL, [$datetime]), + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), ]); if ($data === false) { @@ -226,10 +226,10 @@ protected function hit(string $key, string $datetime): void ]; $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } else { - $data->setAttribute('count', $data->getAttribute('count',0) + 1); + $data->setAttribute('count', $data->getAttribute('count', 0) + 1); $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); } - + Authorization::reset(); $this->count++; @@ -265,17 +265,17 @@ public function getLogs(int $offset, int $limit): array public function cleanup(string $datetime): bool { Authorization::disable(); - + do { $documents = $this->db->find(TimeLimit::COLLECTION, [ - new Query('time', Query::TYPE_LESSER, [$datetime]), + Query::lessThan('time', $datetime), ]); - + foreach ($documents as $document) { $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); } - } while(!empty($documents)); - + } while (!empty($documents)); + Authorization::reset(); return true; From 438925eddc8975b8377d1b0fced74055935870ce Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Aug 2022 01:21:57 +1200 Subject: [PATCH 124/470] Update DB library --- composer.lock | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/composer.lock b/composer.lock index 0edc92121d5..0d13d185193 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": "fb1ac7b869309cf3b3961911571e09a2", + "content-hash": "1dde2801965d70be3dc4c1259498d944", "packages": [ { "name": "composer/package-versions-deprecated", @@ -287,16 +287,16 @@ }, { "name": "utopia-php/cache", - "version": "0.6.0", + "version": "0.6.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3" + "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ea1353a4bbab617e23c865a7c97b60d8074aee3", - "reference": "8ea1353a4bbab617e23c865a7c97b60d8074aee3", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/9889235a6d3da6cbb1f435201529da4d27c30e79", + "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79", "shasum": "" }, "require": { @@ -334,9 +334,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.6.0" + "source": "https://github.com/utopia-php/cache/tree/0.6.1" }, - "time": "2022-04-04T12:30:05+00:00" + "time": "2022-08-10T08:12:46+00:00" }, { "name": "utopia-php/database", @@ -344,12 +344,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2e33ddd74c9950abdacabd5f0745564674c6d74f" + "reference": "4af6d9d1e9ec518e5f87628051132a6402c647fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2e33ddd74c9950abdacabd5f0745564674c6d74f", - "reference": "2e33ddd74c9950abdacabd5f0745564674c6d74f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/4af6d9d1e9ec518e5f87628051132a6402c647fc", + "reference": "4af6d9d1e9ec518e5f87628051132a6402c647fc", "shasum": "" }, "require": { @@ -400,20 +400,20 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/refactor-permissions" }, - "time": "2022-08-09T00:27:04+00:00" + "time": "2022-08-12T13:00:50+00:00" }, { "name": "utopia-php/framework", - "version": "0.20.0", + "version": "0.21.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a" + "reference": "5aa5431788460a782065e42b0e8a35e7f139af2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/beb5e861c7d0a6256a1272e6b9d70b060ca8629a", - "reference": "beb5e861c7d0a6256a1272e6b9d70b060ca8629a", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/5aa5431788460a782065e42b0e8a35e7f139af2f", + "reference": "5aa5431788460a782065e42b0e8a35e7f139af2f", "shasum": "" }, "require": { @@ -447,9 +447,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.20.0" + "source": "https://github.com/utopia-php/framework/tree/0.21.0" }, - "time": "2022-07-30T09:55:28+00:00" + "time": "2022-08-12T11:37:21+00:00" } ], "packages-dev": [ @@ -4079,8 +4079,7 @@ "prefer-lowest": false, "platform": { "php": ">=8.0", - "ext-pdo": "*", - "ext-curl": "*" + "ext-pdo": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" From 5c24996e9fecfed2bb535cfea7a3346aa5958d36 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 13 Aug 2022 01:58:00 +1200 Subject: [PATCH 125/470] Fix type --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e053df06988..0b9b966cfd0 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -77,7 +77,7 @@ public function setup() : void ]), new Document([ '$id' => 'time', - 'type' => Database::VAR_DATETIME, + 'type' => Database::VAR_INTEGER, 'size' => 0, 'required' => true, 'signed' => false, From 617a5bfbdfb308d5b19d0c89f6354c431d12641d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sun, 14 Aug 2022 15:16:22 +1200 Subject: [PATCH 126/470] Revert "Fix type" This reverts commit 5c24996e9fecfed2bb535cfea7a3346aa5958d36. --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 0b9b966cfd0..e053df06988 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -77,7 +77,7 @@ public function setup() : void ]), new Document([ '$id' => 'time', - 'type' => Database::VAR_INTEGER, + 'type' => Database::VAR_DATETIME, 'size' => 0, 'required' => true, 'signed' => false, From 857af4a8265d8c52537c0c4c7a4574dd17e6b46d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sun, 14 Aug 2022 15:33:16 +1200 Subject: [PATCH 127/470] Update lock --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 0d13d185193..8538e3fbf6c 100644 --- a/composer.lock +++ b/composer.lock @@ -344,12 +344,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4af6d9d1e9ec518e5f87628051132a6402c647fc" + "reference": "33557bdd6ea9e884f583c288d0711be18cbd15f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4af6d9d1e9ec518e5f87628051132a6402c647fc", - "reference": "4af6d9d1e9ec518e5f87628051132a6402c647fc", + "url": "https://api.github.com/repos/utopia-php/database/zipball/33557bdd6ea9e884f583c288d0711be18cbd15f9", + "reference": "33557bdd6ea9e884f583c288d0711be18cbd15f9", "shasum": "" }, "require": { @@ -400,7 +400,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/refactor-permissions" }, - "time": "2022-08-12T13:00:50+00:00" + "time": "2022-08-14T03:11:23+00:00" }, { "name": "utopia-php/framework", From 42fb9a3c15500d586037efd963ece689972de865 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 15 Aug 2022 19:28:30 +1200 Subject: [PATCH 128/470] Fix DB version --- composer.json | 8 +------- composer.lock | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index f1bde5732ba..7594e00ae7d 100755 --- a/composer.json +++ b/composer.json @@ -18,14 +18,8 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "0.20.0" }, - "repositories": [ - { - "url": "https://github.com/utopia-php/database.git", - "type": "git" - } - ], "require-dev": { "phpunit/phpunit": "^9.4", "vimeo/psalm": "4.0.1" diff --git a/composer.lock b/composer.lock index bfe203061d2..87c30890cc5 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": "586e74326f3098413e921f3af9364e13", + "content-hash": "40bdabe1101476f1d49b18279e5a1a03", "packages": [ { "name": "composer/package-versions-deprecated", @@ -346,6 +346,12 @@ "url": "https://github.com/utopia-php/database.git", "reference": "cd89b41564223cddf7d87a41bbaf736a0c89f327" }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/cd89b41564223cddf7d87a41bbaf736a0c89f327", + "reference": "cd89b41564223cddf7d87a41bbaf736a0c89f327", + "shasum": "" + }, "require": { "ext-mongodb": "*", "ext-pdo": "*", @@ -368,11 +374,7 @@ "Utopia\\Database\\": "src/Database" } }, - "autoload-dev": { - "psr-4": { - "Utopia\\Tests\\": "tests/Database" - } - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -394,6 +396,10 @@ "upf", "utopia" ], + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.20.0" + }, "time": "2022-08-14T15:22:34+00:00" }, { From b749a8b43360f6e71f56983da41648726328f7a6 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Aug 2022 23:51:33 +0300 Subject: [PATCH 129/470] abuse timezone format --- composer.json | 2 +- composer.lock | 29 +++++++++++++++++++---------- src/Abuse/Adapters/TimeLimit.php | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 7594e00ae7d..9a6afabe058 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.20.0" + "utopia-php/database": "dev-datetime-tz-filter as 0.20.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 87c30890cc5..c28227952b8 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": "40bdabe1101476f1d49b18279e5a1a03", + "content-hash": "a963025f8709ab052c26f0860403bf27", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.20.0", + "version": "dev-datetime-tz-filter", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "cd89b41564223cddf7d87a41bbaf736a0c89f327" + "reference": "8e4e9a02f829440811cfb2e0a56f40c29519049b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/cd89b41564223cddf7d87a41bbaf736a0c89f327", - "reference": "cd89b41564223cddf7d87a41bbaf736a0c89f327", + "url": "https://api.github.com/repos/utopia-php/database/zipball/8e4e9a02f829440811cfb2e0a56f40c29519049b", + "reference": "8e4e9a02f829440811cfb2e0a56f40c29519049b", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.20.0" + "source": "https://github.com/utopia-php/database/tree/datetime-tz-filter" }, - "time": "2022-08-14T15:22:34+00:00" + "time": "2022-08-15T13:48:34+00:00" }, { "name": "utopia-php/framework", @@ -4070,9 +4070,18 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-datetime-tz-filter", + "alias": "0.20.0", + "alias_normalized": "0.20.0.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4081,5 +4090,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index adbf8879761..e01b3283a59 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -91,7 +91,7 @@ public function setup(): void 'required' => true, 'signed' => false, 'array' => false, - 'filters' => [], + 'filters' => ['datetime'], ]), new Document([ '$id' => 'count', From e98f7eb51c0169fe6a0cb4e4a24f762b4516a9a8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 15 Aug 2022 23:57:48 +0300 Subject: [PATCH 130/470] abuse timezone format --- tests/Abuse/AbuseTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 763675bb3cb..4365a77cadc 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -75,6 +75,7 @@ public function testCleanup() // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); + var_dump($logs); $this->assertEquals(1, \count($logs)); sleep(5); From 1a7a6e21e8ba80b88e85f53157fc81feffcb2db4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Aug 2022 00:11:06 +0300 Subject: [PATCH 131/470] abuse timezone format --- tests/Abuse/AbuseTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 4365a77cadc..763675bb3cb 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -75,7 +75,6 @@ public function testCleanup() // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); - var_dump($logs); $this->assertEquals(1, \count($logs)); sleep(5); From 038a59f5c0f051868a749cde131e3dcec60b8ea9 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 16 Aug 2022 00:16:19 +0300 Subject: [PATCH 132/470] getLogs fix --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e01b3283a59..a0beaa1d266 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -256,7 +256,7 @@ protected function hit(string $key, string $datetime): void public function getLogs(int $offset, int $limit): array { return Authorization::skip(function () use ($offset, $limit) { - return $this->db->find(TimeLimit::COLLECTION, [], $limit, $offset, ['_id'], ['DESC']); + return $this->db->find(TimeLimit::COLLECTION, [Query::limit($limit), Query::offset($offset), Query::orderDesc('')]); }); } From 76403074e539705dc8ad6d31413146ed7731cea5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Aug 2022 16:05:43 +0300 Subject: [PATCH 133/470] database 0.22.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9a6afabe058..7d5158cc319 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "dev-datetime-tz-filter as 0.20.0" + "utopia-php/database": "0.22.0" }, "require-dev": { "phpunit/phpunit": "^9.4", From 3afc5acfd3de98ce3933e08ec7c11363396a0359 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Aug 2022 16:19:25 +0300 Subject: [PATCH 134/470] database 0.22.0 --- composer.lock | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index c28227952b8..634f58817ec 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": "a963025f8709ab052c26f0860403bf27", + "content-hash": "8dc54e5af137cd6e57a5b03de53f24cb", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "dev-datetime-tz-filter", + "version": "0.22.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "8e4e9a02f829440811cfb2e0a56f40c29519049b" + "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/8e4e9a02f829440811cfb2e0a56f40c29519049b", - "reference": "8e4e9a02f829440811cfb2e0a56f40c29519049b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/22c45ae83612e907203b7571cd8e3115ae3ae4c5", + "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/datetime-tz-filter" + "source": "https://github.com/utopia-php/database/tree/0.22.0" }, - "time": "2022-08-15T13:48:34+00:00" + "time": "2022-08-17T12:55:37+00:00" }, { "name": "utopia-php/framework", @@ -4070,18 +4070,9 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-datetime-tz-filter", - "alias": "0.20.0", - "alias_normalized": "0.20.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From a2a371b3a0ba9d9c9b8e2ea51595a6f3c74c9659 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 19 Aug 2022 08:19:51 +0000 Subject: [PATCH 135/470] feat: update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 7d5158cc319..733d2e99b43 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.22.0" + "utopia-php/database": "0.23.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 634f58817ec..54f202256f5 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": "8dc54e5af137cd6e57a5b03de53f24cb", + "content-hash": "ffef65d3fde6197a4d450f4b813887dc", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.22.0", + "version": "0.23.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5" + "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/22c45ae83612e907203b7571cd8e3115ae3ae4c5", - "reference": "22c45ae83612e907203b7571cd8e3115ae3ae4c5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", + "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.22.0" + "source": "https://github.com/utopia-php/database/tree/0.23.0" }, - "time": "2022-08-17T12:55:37+00:00" + "time": "2022-08-18T19:08:33+00:00" }, { "name": "utopia-php/framework", From 1361d0db583cf91d1766307e330b9a36ebf14472 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Sat, 27 Aug 2022 09:19:48 +0000 Subject: [PATCH 136/470] chore: bump database --- composer.json | 2 +- composer.lock | 135 ++++++++++++-------------------------------------- 2 files changed, 33 insertions(+), 104 deletions(-) diff --git a/composer.json b/composer.json index 733d2e99b43..05d79b916e8 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.23.0" + "utopia-php/database": "0.24.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 54f202256f5..ac6f0ff8791 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": "ffef65d3fde6197a4d450f4b813887dc", + "content-hash": "e9f782d9d9b6fbe5a6ee7bd609493f61", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.23.0", + "version": "0.24.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71" + "reference": "7da841d65d87e9f2c242589e58c38880def44dd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", - "reference": "e4a23f8e26a3d96f292e549cebe0ff6937ec5d71", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7da841d65d87e9f2c242589e58c38880def44dd8", + "reference": "7da841d65d87e9f2c242589e58c38880def44dd8", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.23.0" + "source": "https://github.com/utopia-php/database/tree/0.24.0" }, - "time": "2022-08-18T19:08:33+00:00" + "time": "2022-08-27T09:16:05+00:00" }, { "name": "utopia-php/framework", @@ -1462,92 +1462,25 @@ }, "time": "2022-03-15T21:29:03+00:00" }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073", + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1596,7 +1529,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16" }, "funding": [ { @@ -1604,7 +1537,7 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-20T05:26:47+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1849,16 +1782,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.21", + "version": "9.5.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + "reference": "888556852e7e9bbeeedb9656afe46118765ade34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34", + "reference": "888556852e7e9bbeeedb9656afe46118765ade34", "shasum": "" }, "require": { @@ -1873,7 +1806,6 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", @@ -1891,9 +1823,6 @@ "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" @@ -1935,7 +1864,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23" }, "funding": [ { @@ -1947,7 +1876,7 @@ "type": "github" } ], - "time": "2022-06-19T12:14:25+00:00" + "time": "2022-08-22T14:01:36+00:00" }, { "name": "psr/container", @@ -3018,16 +2947,16 @@ }, { "name": "symfony/console", - "version": "v5.4.11", + "version": "v5.4.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", - "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", "shasum": "" }, "require": { @@ -3097,7 +3026,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.11" + "source": "https://github.com/symfony/console/tree/v5.4.12" }, "funding": [ { @@ -3113,7 +3042,7 @@ "type": "tidelift" } ], - "time": "2022-07-22T10:42:43+00:00" + "time": "2022-08-17T13:18:05+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3678,16 +3607,16 @@ }, { "name": "symfony/string", - "version": "v6.1.3", + "version": "v6.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b" + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f35241f45c30bcd9046af2bb200a7086f70e1d6b", - "reference": "f35241f45c30bcd9046af2bb200a7086f70e1d6b", + "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c", + "reference": "290972cad7b364e3befaa74ba0ec729800fb161c", "shasum": "" }, "require": { @@ -3743,7 +3672,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.3" + "source": "https://github.com/symfony/string/tree/v6.1.4" }, "funding": [ { @@ -3759,7 +3688,7 @@ "type": "tidelift" } ], - "time": "2022-07-27T15:50:51+00:00" + "time": "2022-08-12T18:05:43+00:00" }, { "name": "theseer/tokenizer", From deca9dba2cad201985565b2c589a33de2f7928b5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 6 Sep 2022 19:59:49 +1200 Subject: [PATCH 137/470] Bump database --- composer.json | 2 +- composer.lock | 68 +++++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/composer.json b/composer.json index 05d79b916e8..19536d7225f 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.24.0" + "utopia-php/database": "0.25.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index ac6f0ff8791..672c0f56903 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": "e9f782d9d9b6fbe5a6ee7bd609493f61", + "content-hash": "f9439322073cbeb6a6252fbca6c26f64", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.24.0", + "version": "0.25.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7da841d65d87e9f2c242589e58c38880def44dd8" + "reference": "167fb53a6c199d4b9ce5fc513c78774ee92199e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7da841d65d87e9f2c242589e58c38880def44dd8", - "reference": "7da841d65d87e9f2c242589e58c38880def44dd8", + "url": "https://api.github.com/repos/utopia-php/database/zipball/167fb53a6c199d4b9ce5fc513c78774ee92199e7", + "reference": "167fb53a6c199d4b9ce5fc513c78774ee92199e7", "shasum": "" }, "require": { @@ -398,9 +398,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.24.0" + "source": "https://github.com/utopia-php/database/tree/0.25.0" }, - "time": "2022-08-27T09:16:05+00:00" + "time": "2022-09-06T07:53:26+00:00" }, { "name": "utopia-php/framework", @@ -1084,16 +1084,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -1134,9 +1134,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2022-05-31T20:59:12+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { "name": "openlss/lib-array2xml", @@ -1464,16 +1464,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.16", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073", - "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { @@ -1529,7 +1529,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" }, "funding": [ { @@ -1537,7 +1537,7 @@ "type": "github" } ], - "time": "2022-08-20T05:26:47+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1782,16 +1782,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.23", + "version": "9.5.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "888556852e7e9bbeeedb9656afe46118765ade34" + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34", - "reference": "888556852e7e9bbeeedb9656afe46118765ade34", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", "shasum": "" }, "require": { @@ -1820,7 +1820,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "sebastian/type": "^3.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1864,7 +1864,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" }, "funding": [ { @@ -1876,7 +1876,7 @@ "type": "github" } ], - "time": "2022-08-22T14:01:36+00:00" + "time": "2022-08-30T07:42:16+00:00" }, { "name": "psr/container", @@ -2838,16 +2838,16 @@ }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "fb44e1cc6e557418387ad815780360057e40753e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e", + "reference": "fb44e1cc6e557418387ad815780360057e40753e", "shasum": "" }, "require": { @@ -2859,7 +2859,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -2882,7 +2882,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.1.0" }, "funding": [ { @@ -2890,7 +2890,7 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2022-08-29T06:55:37+00:00" }, { "name": "sebastian/version", @@ -4010,5 +4010,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From 0a950f4a37fc88985bd0464053608c33882cb948 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 7 Sep 2022 18:16:49 +0300 Subject: [PATCH 138/470] database 0.22.0 --- composer.json | 2 +- composer.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 19536d7225f..c6df3c09dab 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.25.0" + "utopia-php/database": "0.25.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 672c0f56903..87d3924c66b 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": "f9439322073cbeb6a6252fbca6c26f64", + "content-hash": "1c2d842158fd4096a1b63705a1579b78", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.25.0", + "version": "0.25.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "167fb53a6c199d4b9ce5fc513c78774ee92199e7" + "reference": "9d013ce3c111d1477d7986483f1003dcab2b9d14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/167fb53a6c199d4b9ce5fc513c78774ee92199e7", - "reference": "167fb53a6c199d4b9ce5fc513c78774ee92199e7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/9d013ce3c111d1477d7986483f1003dcab2b9d14", + "reference": "9d013ce3c111d1477d7986483f1003dcab2b9d14", "shasum": "" }, "require": { @@ -398,22 +398,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.25.0" + "source": "https://github.com/utopia-php/database/tree/0.25.1" }, - "time": "2022-09-06T07:53:26+00:00" + "time": "2022-09-07T14:47:52+00:00" }, { "name": "utopia-php/framework", - "version": "0.21.0", + "version": "0.21.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "5aa5431788460a782065e42b0e8a35e7f139af2f" + "reference": "c81789b87a917da2daf336738170ebe01f50ea18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/5aa5431788460a782065e42b0e8a35e7f139af2f", - "reference": "5aa5431788460a782065e42b0e8a35e7f139af2f", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/c81789b87a917da2daf336738170ebe01f50ea18", + "reference": "c81789b87a917da2daf336738170ebe01f50ea18", "shasum": "" }, "require": { @@ -447,9 +447,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.21.0" + "source": "https://github.com/utopia-php/framework/tree/0.21.1" }, - "time": "2022-08-12T11:37:21+00:00" + "time": "2022-09-07T09:56:28+00:00" } ], "packages-dev": [ @@ -4010,5 +4010,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } From d6461437a98fe057addd3e5cce8c1fbac7ade1af Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 14 Oct 2022 11:19:21 +0000 Subject: [PATCH 139/470] upgrade database --- composer.json | 2 +- composer.lock | 114 ++++++++++++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/composer.json b/composer.json index c6df3c09dab..b4ae46c3207 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.25.*" + "utopia-php/database": "0.26.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 87d3924c66b..58209e0fa64 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": "1c2d842158fd4096a1b63705a1579b78", + "content-hash": "c141dfb7f633587b3179a7604b93c205", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,16 +340,16 @@ }, { "name": "utopia-php/database", - "version": "0.25.1", + "version": "0.26.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "9d013ce3c111d1477d7986483f1003dcab2b9d14" + "reference": "d172af2541137c83a86d066f82f48914b5a3a610" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/9d013ce3c111d1477d7986483f1003dcab2b9d14", - "reference": "9d013ce3c111d1477d7986483f1003dcab2b9d14", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d172af2541137c83a86d066f82f48914b5a3a610", + "reference": "d172af2541137c83a86d066f82f48914b5a3a610", "shasum": "" }, "require": { @@ -398,30 +398,30 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.25.1" + "source": "https://github.com/utopia-php/database/tree/0.26.0" }, - "time": "2022-09-07T14:47:52+00:00" + "time": "2022-10-03T17:12:01+00:00" }, { "name": "utopia-php/framework", - "version": "0.21.1", + "version": "0.22.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "c81789b87a917da2daf336738170ebe01f50ea18" + "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/c81789b87a917da2daf336738170ebe01f50ea18", - "reference": "c81789b87a917da2daf336738170ebe01f50ea18", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", + "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", "shasum": "" }, "require": { "php": ">=8.0.0" }, "require-dev": { - "phpunit/phpunit": "^9.5.10", - "vimeo/psalm": "4.13.1" + "phpunit/phpunit": "^9.5.25", + "vimeo/psalm": "^4.27.0" }, "type": "library", "autoload": { @@ -447,9 +447,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.21.1" + "source": "https://github.com/utopia-php/framework/tree/0.22.1" }, - "time": "2022-09-07T09:56:28+00:00" + "time": "2022-10-07T14:51:40+00:00" } ], "packages-dev": [ @@ -1782,16 +1782,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.24", + "version": "9.5.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", "shasum": "" }, "require": { @@ -1813,14 +1813,14 @@ "phpunit/php-timer": "^5.0.2", "sebastian/cli-parser": "^1.0.1", "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", + "sebastian/comparator": "^4.0.8", "sebastian/diff": "^4.0.3", "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", + "sebastian/exporter": "^4.0.5", "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.1", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1864,7 +1864,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" }, "funding": [ { @@ -1874,9 +1874,13 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-08-30T07:42:16+00:00" + "time": "2022-09-25T03:44:45+00:00" }, { "name": "psr/container", @@ -2150,16 +2154,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -2212,7 +2216,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -2220,7 +2224,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -2410,16 +2414,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -2475,7 +2479,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -2483,7 +2487,7 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -2838,16 +2842,16 @@ }, { "name": "sebastian/type", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb44e1cc6e557418387ad815780360057e40753e" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb44e1cc6e557418387ad815780360057e40753e", - "reference": "fb44e1cc6e557418387ad815780360057e40753e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -2859,7 +2863,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -2882,7 +2886,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -2890,7 +2894,7 @@ "type": "github" } ], - "time": "2022-08-29T06:55:37+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", @@ -2947,16 +2951,16 @@ }, { "name": "symfony/console", - "version": "v5.4.12", + "version": "v5.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" + "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "url": "https://api.github.com/repos/symfony/console/zipball/984ea2c0f45f42dfed01d2f3987b187467c4b16d", + "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d", "shasum": "" }, "require": { @@ -3026,7 +3030,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.12" + "source": "https://github.com/symfony/console/tree/v5.4.14" }, "funding": [ { @@ -3042,7 +3046,7 @@ "type": "tidelift" } ], - "time": "2022-08-17T13:18:05+00:00" + "time": "2022-10-07T08:01:20+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3607,16 +3611,16 @@ }, { "name": "symfony/string", - "version": "v6.1.4", + "version": "v6.1.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "290972cad7b364e3befaa74ba0ec729800fb161c" + "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c", - "reference": "290972cad7b364e3befaa74ba0ec729800fb161c", + "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", + "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", "shasum": "" }, "require": { @@ -3672,7 +3676,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.4" + "source": "https://github.com/symfony/string/tree/v6.1.6" }, "funding": [ { @@ -3688,7 +3692,7 @@ "type": "tidelift" } ], - "time": "2022-08-12T18:05:43+00:00" + "time": "2022-10-10T09:34:31+00:00" }, { "name": "theseer/tokenizer", From 1cb10a67f14442487824c8ec3dcc5a0d09082046 Mon Sep 17 00:00:00 2001 From: mohith2883 <88938356+mohith2883@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:51:30 +0530 Subject: [PATCH 140/470] added codeql-analysis.yml and phpstan --- .github/workflows/codeql-analysis.yml | 20 ++++++++++++++++++++ composer.json | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..734db04cc84 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,20 @@ +name: "CodeQL" + +on: [pull_request] +jobs: + lint: + name: CodeQL + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - run: git checkout HEAD^2 + + - name: Run CodeQL + run: | + docker run --rm -v $PWD:/app composer sh -c \ + "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file diff --git a/composer.json b/composer.json index b4ae46c3207..f8caadd960b 100755 --- a/composer.json +++ b/composer.json @@ -11,6 +11,9 @@ "email": "eldad@appwrite.io" } ], + "scripts": { + "check": "./vendor/bin/phpstan analyse --level max src tests" + }, "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, @@ -22,6 +25,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.4", - "vimeo/psalm": "4.0.1" + "vimeo/psalm": "4.0.1", + "phpstan/phpstan": "1.9.x-dev" } } From e1ac06d2c4b055cb66060fd88b3f590251be3214 Mon Sep 17 00:00:00 2001 From: Tiansing Date: Sat, 22 Oct 2022 10:56:14 +0800 Subject: [PATCH 141/470] [Library updated] Authors Removed --- README.md | 10 +- composer.json | 6 - composer.lock | 537 +------------------------------------- tests/Abuse/AbuseTest.php | 4 +- 4 files changed, 8 insertions(+), 549 deletions(-) diff --git a/README.md b/README.md index 476c159d071..26672b54904 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Although this library is part of the [Utopia Framework](https://github.com/utopi ## Getting Started Install using composer: + ```bash composer require utopia-php/abuse ``` @@ -87,19 +88,12 @@ if(!$abuse->check()) { } ``` -*Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header.* +_Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header._ ## System Requirements Utopia Framework requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible. -## Authors - -**Eldad Fux** - -+ [https://twitter.com/eldadfux](https://twitter.com/eldadfux) -+ [https://github.com/eldadfux](https://github.com/eldadfux) - ## Copyright and license The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) diff --git a/composer.json b/composer.json index b4ae46c3207..9d8c460b910 100755 --- a/composer.json +++ b/composer.json @@ -5,12 +5,6 @@ "keywords": ["php","framework", "upf", "utopia", "abuse"], "license": "MIT", "minimum-stability": "stable", - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, diff --git a/composer.lock b/composer.lock index 58209e0fa64..7099b881b5a 100644 --- a/composer.lock +++ b/composer.lock @@ -48,16 +48,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", @@ -115,12 +105,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], "description": "A wrapper for ocramius/package-versions to get pretty versions strings", "keywords": [ "composer", @@ -178,16 +162,6 @@ "license": [ "Apache-2.0" ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], "description": "MongoDB driver library", "homepage": "https://jira.mongodb.org/browse/PHPLIB", "keywords": [ @@ -243,21 +217,7 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], + ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ @@ -317,13 +277,7 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], + ], "description": "A simple cache library to manage application cache storing, loading and purging", "keywords": [ "cache", @@ -377,17 +331,7 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Brandon Leckemby", - "email": "brandon@appwrite.io" - } - ], + ], "description": "A simple library to manage application persistency using multiple database adapters", "keywords": [ "database", @@ -432,13 +376,7 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], + ], "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", @@ -498,24 +436,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], "description": "A non-blocking concurrency framework for PHP applications.", "homepage": "https://amphp.org/amp", "keywords": [ @@ -586,16 +506,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], "description": "A stream abstraction to make working with non-blocking I/O simple.", "homepage": "http://amphp.org/byte-stream", "keywords": [ @@ -655,23 +565,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", @@ -732,12 +625,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", @@ -838,13 +725,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ @@ -903,12 +783,6 @@ "license": [ "ISC" ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], "description": "A more advanced JSONRPC implementation", "support": { "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", @@ -953,12 +827,6 @@ "license": [ "ISC" ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], "description": "PHP classes for the Language Server Protocol", "keywords": [ "language", @@ -1066,14 +934,6 @@ "license": [ "OSL-3.0" ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], "description": "Map nested JSON structures onto PHP classes", "support": { "email": "cweiske@cweiske.de", @@ -1122,11 +982,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Nikita Popov" - } - ], "description": "A PHP parser written in PHP", "keywords": [ "parser", @@ -1165,18 +1020,6 @@ "license": [ "Apache-2.0" ], - "authors": [ - { - "name": "Bryan Tong", - "email": "bryan@nullivex.com", - "homepage": "https://www.nullivex.com" - }, - { - "name": "Tony Butler", - "email": "spudz76@gmail.com", - "homepage": "https://www.nullivex.com" - } - ], "description": "Array2XML conversion library credit to lalit.org", "homepage": "https://www.nullivex.com", "keywords": [ @@ -1227,23 +1070,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", @@ -1278,23 +1104,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", @@ -1334,12 +1143,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], "description": "Common reflection classes used by phpdocumentor to reflect the code structure", "homepage": "http://www.phpdoc.org", "keywords": [ @@ -1395,16 +1198,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", @@ -1449,12 +1242,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", @@ -1513,13 +1300,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ @@ -1574,13 +1354,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ @@ -1638,13 +1411,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Invoke callables with a timeout", "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ @@ -1697,13 +1463,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Simple template engine.", "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ @@ -1756,13 +1515,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Utility class for timing", "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ @@ -1848,13 +1600,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", "keywords": [ @@ -1914,12 +1659,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], "description": "Common Container Interface (PHP FIG PSR-11)", "homepage": "https://github.com/php-fig/container", "keywords": [ @@ -1967,12 +1706,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", "keywords": [ @@ -2020,13 +1753,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { @@ -2076,13 +1802,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { @@ -2132,12 +1851,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { @@ -2189,24 +1902,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], "description": "Provides the functionality to compare PHP values for equality", "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ @@ -2262,13 +1957,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", "support": { @@ -2319,16 +2007,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ @@ -2387,12 +2065,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Provides functionality to handle HHVM/PHP environments", "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ @@ -2449,28 +2121,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], "description": "Provides the functionality to export PHP variables for visualization", "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ @@ -2530,12 +2180,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Snapshotting of global state", "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ @@ -2589,13 +2233,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { @@ -2647,12 +2284,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { @@ -2702,12 +2333,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { @@ -2757,20 +2382,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { @@ -2820,12 +2431,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { @@ -2875,13 +2480,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", "support": { @@ -2928,13 +2526,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", "support": { @@ -3011,16 +2602,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ @@ -3084,16 +2665,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { @@ -3160,16 +2731,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ @@ -3239,16 +2800,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ @@ -3323,16 +2874,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ @@ -3407,16 +2948,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ @@ -3487,16 +3018,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ @@ -3570,16 +3091,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ @@ -3655,16 +3166,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ @@ -3724,13 +3225,6 @@ "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", @@ -3832,11 +3326,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Matthew Brown" - } - ], "description": "A static analysis tool for finding errors in PHP applications", "keywords": [ "code", @@ -3884,12 +3373,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ "assert", @@ -3938,12 +3421,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], "description": "A PHP implementation of Ant's glob.", "support": { "issues": "https://github.com/webmozarts/glob/issues", @@ -3988,12 +3465,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", "support": { "issues": "https://github.com/webmozart/path-util/issues", diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 763675bb3cb..bafd9db4790 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -1,4 +1,5 @@ * @version 1.0 RC4 * @license The MIT License (MIT) */ @@ -46,7 +46,7 @@ public function setUp(): void $db->setNamespace('namespace'); $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - if(!$db->exists('utopiaTests')) { + if (!$db->exists('utopiaTests')) { $db->create('utopiaTests'); $adapter->setup(); } From c3ca59aa38918f8716c076c027e61d278ad2b926 Mon Sep 17 00:00:00 2001 From: Christian Igbaras <59194525+Tiansing@users.noreply.github.com> Date: Fri, 21 Oct 2022 20:09:22 -0700 Subject: [PATCH 142/470] asterisk added to notice --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26672b54904..b1e777d53c8 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ if(!$abuse->check()) { } ``` -_Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header._ +*Notice: The code above is for example purpose only. It is always recommended to validate user input before using it in your code. If you are using a load balancer or any proxy server you might need to get user IP from the HTTP_X_FORWARDE‌​D_FOR header.* ## System Requirements From d278fddf53f0bac402fb3db022f58a242cb1745b Mon Sep 17 00:00:00 2001 From: Christian Igbaras <59194525+Tiansing@users.noreply.github.com> Date: Thu, 27 Oct 2022 03:09:08 -0700 Subject: [PATCH 143/470] Comment block removed! --- tests/Abuse/AbuseTest.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index bafd9db4790..728b815cd88 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -1,16 +1,5 @@ - */ - namespace Utopia\Tests; use PDO; From db629a36061795fdf2704dab51231a6e94a3e91f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 30 Oct 2022 06:04:41 +0000 Subject: [PATCH 144/470] upgrade database --- composer.json | 2 +- composer.lock | 574 +++++++++++++++++++------------------------------- 2 files changed, 219 insertions(+), 357 deletions(-) diff --git a/composer.json b/composer.json index b4ae46c3207..75aa1f55d8b 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.26.*" + "utopia-php/database": "0.27.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 58209e0fa64..11fd9b77f9b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,307 +4,30 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c141dfb7f633587b3179a7604b93c205", + "content-hash": "a9b90a9b636834127212357b3ec42362", "packages": [ - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, - { - "name": "jean85/pretty-package-versions", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303", - "shasum": "" - }, - "require": { - "composer/package-versions-deprecated": "^1.8.0", - "php": "^7.0|^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0|^8.5|^9.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A wrapper for ocramius/package-versions to get pretty versions strings", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" - }, - "time": "2021-02-04T16:20:16+00:00" - }, - { - "name": "mongodb/mongodb", - "version": "1.8.0", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d", - "shasum": "" - }, - "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mongodb": "^1.8.1", - "jean85/pretty-package-versions": "^1.2", - "php": "^7.0 || ^8.0", - "symfony/polyfill-php80": "^1.19" - }, - "require-dev": { - "squizlabs/php_codesniffer": "^3.5, <3.5.5", - "symfony/phpunit-bridge": "5.x-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" - }, - "time": "2020-11-25T12:26:02+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-10T07:21:04+00:00" - }, { "name": "utopia-php/cache", - "version": "0.6.1", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79" + "reference": "cd53431242c88299daea2589e21322abe97682cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/9889235a6d3da6cbb1f435201529da4d27c30e79", - "reference": "9889235a6d3da6cbb1f435201529da4d27c30e79", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/cd53431242c88299daea2589e21322abe97682cc", + "reference": "cd53431242c88299daea2589e21322abe97682cc", "shasum": "" }, "require": { "ext-json": "*", + "ext-memcached": "*", "ext-redis": "*", "php": ">=8.0" }, "require-dev": { + "laravel/pint": "1.2.*", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -318,12 +41,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "description": "A simple cache library to manage application cache storing, loading and purging", "keywords": [ "cache", @@ -334,35 +51,35 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.6.1" + "source": "https://github.com/utopia-php/cache/tree/0.7.0" }, - "time": "2022-08-10T08:12:46+00:00" + "time": "2022-10-16T06:04:12+00:00" }, { "name": "utopia-php/database", - "version": "0.26.0", + "version": "0.27.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d172af2541137c83a86d066f82f48914b5a3a610" + "reference": "7e6ee3002a66ba94d1644b6fdfd668248132b527" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d172af2541137c83a86d066f82f48914b5a3a610", - "reference": "d172af2541137c83a86d066f82f48914b5a3a610", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7e6ee3002a66ba94d1644b6fdfd668248132b527", + "reference": "7e6ee3002a66ba94d1644b6fdfd668248132b527", "shasum": "" }, "require": { - "ext-mongodb": "*", - "ext-pdo": "*", - "ext-redis": "*", - "mongodb/mongodb": "1.8.0", "php": ">=8.0", - "utopia-php/cache": "0.6.*", + "utopia-php/cache": "0.7.*", "utopia-php/framework": "0.*.*" }, "require-dev": { + "ext-mongodb": "*", + "ext-pdo": "*", + "ext-redis": "*", "fakerphp/faker": "^1.14", + "mongodb/mongodb": "1.8.0", "phpunit/phpunit": "^9.4", "swoole/ide-helper": "4.8.0", "utopia-php/cli": "^0.11.0", @@ -378,16 +95,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Brandon Leckemby", - "email": "brandon@appwrite.io" - } - ], "description": "A simple library to manage application persistency using multiple database adapters", "keywords": [ "database", @@ -398,22 +105,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.26.0" + "source": "https://github.com/utopia-php/database/tree/0.27.1" }, - "time": "2022-10-03T17:12:01+00:00" + "time": "2022-10-26T18:19:32+00:00" }, { "name": "utopia-php/framework", - "version": "0.22.1", + "version": "0.23.2", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df" + "reference": "847cfd48fa93f25797dea0be5692f5c3735c9b2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", - "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/847cfd48fa93f25797dea0be5692f5c3735c9b2f", + "reference": "847cfd48fa93f25797dea0be5692f5c3735c9b2f", "shasum": "" }, "require": { @@ -433,12 +140,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", @@ -447,9 +148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.22.1" + "source": "https://github.com/utopia-php/framework/tree/0.23.2" }, - "time": "2022-10-07T14:51:40+00:00" + "time": "2022-10-25T15:05:52+00:00" } ], "packages-dev": [ @@ -619,6 +320,79 @@ ], "time": "2021-03-30T17:13:30+00:00" }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, { "name": "composer/semver", "version": "3.3.2", @@ -1414,25 +1188,30 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -1458,22 +1237,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2022-10-14T12:47:21+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "9.2.18", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", "shasum": "" }, "require": { @@ -1529,7 +1308,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" }, "funding": [ { @@ -1537,7 +1316,7 @@ "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2022-10-27T13:35:33+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1782,16 +1561,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.5.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", "shasum": "" }, "require": { @@ -1864,7 +1643,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" }, "funding": [ { @@ -1880,7 +1659,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2022-10-28T06:00:21+00:00" }, { "name": "psr/container", @@ -2951,16 +2730,16 @@ }, { "name": "symfony/console", - "version": "v5.4.14", + "version": "v5.4.15", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d" + "reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/984ea2c0f45f42dfed01d2f3987b187467c4b16d", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d", + "url": "https://api.github.com/repos/symfony/console/zipball/ea59bb0edfaf9f28d18d8791410ee0355f317669", + "reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669", "shasum": "" }, "require": { @@ -3030,7 +2809,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.14" + "source": "https://github.com/symfony/console/tree/v5.4.15" }, "funding": [ { @@ -3046,7 +2825,7 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:01:20+00:00" + "time": "2022-10-26T21:41:52+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3524,6 +3303,89 @@ ], "time": "2022-05-24T11:49:31+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.1.1", @@ -3611,16 +3473,16 @@ }, { "name": "symfony/string", - "version": "v6.1.6", + "version": "v6.1.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", + "url": "https://api.github.com/repos/symfony/string/zipball/823f143370880efcbdfa2dbca946b3358c4707e5", + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5", "shasum": "" }, "require": { @@ -3676,7 +3538,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.6" + "source": "https://github.com/symfony/string/tree/v6.1.7" }, "funding": [ { From ff81e51b8e43ca0793d4c682fe75a8e824265b5e Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 31 Oct 2022 16:21:00 +0200 Subject: [PATCH 145/470] composer upgrade --- composer.json | 2 +- composer.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index 75aa1f55d8b..3d3fea1acbf 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.27.*" + "utopia-php/database": "0.28.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 11fd9b77f9b..4991d5a4690 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a9b90a9b636834127212357b3ec42362", + "content-hash": "a62cb0b4e78a919c5e3ede9f1d0eb8f1", "packages": [ { "name": "utopia-php/cache", - "version": "0.7.0", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "cd53431242c88299daea2589e21322abe97682cc" + "reference": "212e66100a1f32e674fca5d9bc317cc998303089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/cd53431242c88299daea2589e21322abe97682cc", - "reference": "cd53431242c88299daea2589e21322abe97682cc", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/212e66100a1f32e674fca5d9bc317cc998303089", + "reference": "212e66100a1f32e674fca5d9bc317cc998303089", "shasum": "" }, "require": { @@ -51,27 +51,27 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.7.0" + "source": "https://github.com/utopia-php/cache/tree/0.8.0" }, - "time": "2022-10-16T06:04:12+00:00" + "time": "2022-10-16T16:48:09+00:00" }, { "name": "utopia-php/database", - "version": "0.27.1", + "version": "0.28.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7e6ee3002a66ba94d1644b6fdfd668248132b527" + "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7e6ee3002a66ba94d1644b6fdfd668248132b527", - "reference": "7e6ee3002a66ba94d1644b6fdfd668248132b527", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ef6506af1c09c22f5dc1e7859159d323f7fafa94", + "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cache": "0.7.*", + "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*" }, "require-dev": { @@ -105,22 +105,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.27.1" + "source": "https://github.com/utopia-php/database/tree/0.28.0" }, - "time": "2022-10-26T18:19:32+00:00" + "time": "2022-10-31T09:58:46+00:00" }, { "name": "utopia-php/framework", - "version": "0.23.2", + "version": "0.23.4", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "847cfd48fa93f25797dea0be5692f5c3735c9b2f" + "reference": "97f64aa1732af92b967c3576f16967dc762ad47b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/847cfd48fa93f25797dea0be5692f5c3735c9b2f", - "reference": "847cfd48fa93f25797dea0be5692f5c3735c9b2f", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/97f64aa1732af92b967c3576f16967dc762ad47b", + "reference": "97f64aa1732af92b967c3576f16967dc762ad47b", "shasum": "" }, "require": { @@ -148,9 +148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.23.2" + "source": "https://github.com/utopia-php/framework/tree/0.23.4" }, - "time": "2022-10-25T15:05:52+00:00" + "time": "2022-10-31T11:57:14+00:00" } ], "packages-dev": [ From 6e49df23cbed154d2b1b15b1ba966da6010b6ff5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 8 Nov 2022 18:28:55 +0200 Subject: [PATCH 146/470] abuse increase tests --- composer.json | 2 +- composer.lock | 41 +++++++++++++++++++------------- src/Abuse/Adapters/TimeLimit.php | 3 +-- tests/Abuse/AbuseTest.php | 29 ++++++++++++++++++---- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 3d3fea1acbf..802b8ee28cc 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.28.*" + "utopia-php/database": "dev-increment as 0.28.99" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 4991d5a4690..b90b5eda5dd 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": "a62cb0b4e78a919c5e3ede9f1d0eb8f1", + "content-hash": "7931149eb908213613f6e4ddff7f7efb", "packages": [ { "name": "utopia-php/cache", @@ -57,26 +57,26 @@ }, { "name": "utopia-php/database", - "version": "0.28.0", + "version": "dev-increment", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94" + "reference": "051078fa6ec1bdbc3d35c170096656180976cb04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ef6506af1c09c22f5dc1e7859159d323f7fafa94", - "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94", + "url": "https://api.github.com/repos/utopia-php/database/zipball/051078fa6ec1bdbc3d35c170096656180976cb04", + "reference": "051078fa6ec1bdbc3d35c170096656180976cb04", "shasum": "" }, "require": { + "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*" }, "require-dev": { "ext-mongodb": "*", - "ext-pdo": "*", "ext-redis": "*", "fakerphp/faker": "^1.14", "mongodb/mongodb": "1.8.0", @@ -105,22 +105,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.28.0" + "source": "https://github.com/utopia-php/database/tree/increment" }, - "time": "2022-10-31T09:58:46+00:00" + "time": "2022-11-08T13:56:09+00:00" }, { "name": "utopia-php/framework", - "version": "0.23.4", + "version": "0.25.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b" + "reference": "c524f681254255c8204fbf7919c53bf3b4982636" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/97f64aa1732af92b967c3576f16967dc762ad47b", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/c524f681254255c8204fbf7919c53bf3b4982636", + "reference": "c524f681254255c8204fbf7919c53bf3b4982636", "shasum": "" }, "require": { @@ -148,9 +148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.23.4" + "source": "https://github.com/utopia-php/framework/tree/0.25.0" }, - "time": "2022-10-31T11:57:14+00:00" + "time": "2022-11-02T09:49:57+00:00" } ], "packages-dev": [ @@ -3865,9 +3865,18 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-increment", + "alias": "0.28.99", + "alias_normalized": "0.28.99.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index a0beaa1d266..6d2502a8f8d 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -234,8 +234,7 @@ protected function hit(string $key, string $datetime): void ]; $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } else { - $data->setAttribute('count', $data->getAttribute('count', 0) + 1); - $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); } }); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 763675bb3cb..93b4e899cbd 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -23,14 +23,21 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; +use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Limit; +use Utopia\Exception; class AbuseTest extends TestCase { + protected ?Abuse $abuse = null; + protected ?Abuse $abuseRace = null; + /** - * @var Abuse + * @throws Limit + * @throws Exception + * @throws Duplicate + * @throws \Exception */ - protected $abuse = null; - public function setUp(): void { // Limit login attempts to 3 time in 5 minutes time frame @@ -52,8 +59,9 @@ public function setUp(): void } $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); + + $this->abuseRace = new Abuse(new TimeLimit('increase', 999, 60 * 5, $db)); } public function tearDown(): void @@ -70,12 +78,21 @@ public function testIsValid() $this->assertEquals($this->abuse->check(), true); } + public function testAbuse() + { + for($i = 0 ; $i < 999 ; $i++){ + $this->assertEquals($this->abuseRace->check(), false); + } + + $this->assertEquals($this->abuseRace->check(), true); + } + public function testCleanup() { // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); - $this->assertEquals(1, \count($logs)); + $this->assertEquals(2, \count($logs)); sleep(5); // Delete the log @@ -86,4 +103,6 @@ public function testCleanup() $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(0, \count($logs)); } + + } From 47935fb39792b1813c043a863c99661e001c1083 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 8 Nov 2022 18:49:54 +0200 Subject: [PATCH 147/470] abuse increase tests --- tests/Abuse/AbuseTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 93b4e899cbd..ac6ebb43fdb 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -23,7 +23,6 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Limit; use Utopia\Exception; @@ -35,7 +34,6 @@ class AbuseTest extends TestCase /** * @throws Limit * @throws Exception - * @throws Duplicate * @throws \Exception */ public function setUp(): void From e2356b5294f8ea8869f30ed82e86643cdba37750 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 18:36:03 +0200 Subject: [PATCH 148/470] try catch --- src/Abuse/Adapters/TimeLimit.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 6d2502a8f8d..68499af88ad 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -223,7 +223,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - + $data=false; if ($data === false) { $data = [ '$permissions' => [], @@ -232,7 +232,16 @@ protected function hit(string $key, string $datetime): void 'count' => 1, '$collection' => TimeLimit::COLLECTION, ]; - $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + + try { + $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + } catch (Duplicate $e){ + // In case of race Condition + var_dump($e->getCode()); + var_dump($e->getMessage()); + die; + } + } else { $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); } From be5f85dfe563644e0416e5175f8a47e61bb272fe Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 19:10:44 +0200 Subject: [PATCH 149/470] try catch race condition --- docker-compose.yml | 4 ++++ src/Abuse/Adapters/TimeLimit.php | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 034d2b704bd..f913ed29917 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,10 @@ services: - abuse depends_on: - mysql + volumes: + - ./phpunit.xml:/code/phpunit.xml + - ./src:/code/src + - ./tests:/code/tests networks: abuse: \ No newline at end of file diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 68499af88ad..7c62980d34c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -236,10 +236,16 @@ protected function hit(string $key, string $datetime): void try { $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } catch (Duplicate $e){ - // In case of race Condition - var_dump($e->getCode()); - var_dump($e->getMessage()); - die; + // In case of race condition + $data = $this->db->findOne(TimeLimit::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), + ]); + + if($data){ + $this->count = $data->getAttribute('count'); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); + } } } else { From eac2ce7c97f4148083efdbb5f8a729618178e321 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 19:22:27 +0200 Subject: [PATCH 150/470] race condition fix --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 7c62980d34c..5e5985e3a4a 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -223,7 +223,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - $data=false; + if ($data === false) { $data = [ '$permissions' => [], From 4df282ce6eb9aea31c86827f3489289991ea8d74 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 19:29:25 +0200 Subject: [PATCH 151/470] Add Exception for Unknown error --- src/Abuse/Adapters/TimeLimit.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 5e5985e3a4a..f0e14f4813a 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -64,7 +64,6 @@ public function __construct(string $key, int $limit, int $seconds, Database $db) } /** - * @throws Limit * @throws Duplicate * @throws Exception|\Exception */ @@ -236,7 +235,7 @@ protected function hit(string $key, string $datetime): void try { $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } catch (Duplicate $e){ - // In case of race condition + // Duplicate in case of race condition $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -245,6 +244,8 @@ protected function hit(string $key, string $datetime): void if($data){ $this->count = $data->getAttribute('count'); $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); + } else { + Throw new \Exception('Document Not Found'); } } From 254b3800b3d03972629c9a6a14b4cdf30d23eb96 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 19:44:02 +0200 Subject: [PATCH 152/470] Add Exception for Unknown error --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f0e14f4813a..84bf6bd5916 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -222,7 +222,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - + if ($data === false) { $data = [ '$permissions' => [], @@ -240,7 +240,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - +var_dump($data); if($data){ $this->count = $data->getAttribute('count'); $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); From 8d34bf47cf7c24b4377d6fe577aa8b10e3d6c2ad Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 20 Nov 2022 19:44:28 +0200 Subject: [PATCH 153/470] remove var dump --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 84bf6bd5916..f0e14f4813a 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -222,7 +222,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - + if ($data === false) { $data = [ '$permissions' => [], @@ -240,7 +240,7 @@ protected function hit(string $key, string $datetime): void Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); -var_dump($data); + if($data){ $this->count = $data->getAttribute('count'); $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); From 90e0376554369530c80498e4e3374388d40c46c4 Mon Sep 17 00:00:00 2001 From: pmohanj Date: Tue, 20 Dec 2022 21:30:58 +0530 Subject: [PATCH 154/470] Implemented linter --- .github/workflows/linter.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/linter.yml diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000000..bca79de9faa --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,20 @@ +name: "Linter" + +on: [pull_request] +jobs: + lint: + name: Linter + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - run: git checkout HEAD^2 + + - name: Run Linter + run: | + docker run --rm -v $PWD:/app composer sh -c \ + "composer install --profile --ignore-platform-reqs && composer lint" From 9e8e7a240504316630564dfb815a86dfa47929e0 Mon Sep 17 00:00:00 2001 From: pmohanj Date: Tue, 20 Dec 2022 21:32:25 +0530 Subject: [PATCH 155/470] Format the code with linter --- composer.json | 7 ++++- src/Abuse/Abuse.php | 14 ++++----- src/Abuse/Adapter.php | 10 +++--- src/Abuse/Adapters/ReCaptcha.php | 50 +++++++++++++++-------------- src/Abuse/Adapters/TimeLimit.php | 54 +++++++++++++++++--------------- tests/Abuse/AbuseTest.php | 8 ++--- 6 files changed, 74 insertions(+), 69 deletions(-) diff --git a/composer.json b/composer.json index 3d3fea1acbf..9f4edb4081e 100755 --- a/composer.json +++ b/composer.json @@ -14,6 +14,10 @@ "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, + "scripts": { + "lint": "./vendor/bin/pint --test", + "format": "./vendor/bin/pint" + }, "require": { "php": ">=8.0", "ext-pdo": "*", @@ -22,6 +26,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.4", - "vimeo/psalm": "4.0.1" + "vimeo/psalm": "4.0.1", + "laravel/pint": "1.2.*" } } diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 10565870942..632bb1967b5 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -10,11 +10,11 @@ class Abuse protected Adapter $adapter; /** - * @param Adapter $adapter + * @param Adapter $adapter */ public function __construct(Adapter $adapter) { - $this->adapter = $adapter; + $this->adapter = $adapter; } /** @@ -34,9 +34,8 @@ public function check(): bool * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * + * @param int $offset + * @param int $limit * @return array */ public function getLogs(int $offset, int $limit): array @@ -47,12 +46,11 @@ public function getLogs(int $offset, int $limit): array /** * Delete all logs older than $datetime * - * @param string $datetime - * + * @param string $datetime * @return bool */ public function cleanup(string $datetime): bool { return $this->adapter->cleanup($datetime); } -} \ No newline at end of file +} diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 9ea49d1a7af..a49cf1e2296 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -18,19 +18,17 @@ public function check(): bool; * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * + * @param int $offset + * @param int $limit * @return array */ public function getLogs(int $offset, int $limit): array; - /** * Delete all logs older than $datetime * - * @param string $datetime + * @param string $datetime * @return bool */ public function cleanup(string $datetime): bool; -} \ No newline at end of file +} diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 8f2e4340ede..7d9562868a8 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -33,18 +33,19 @@ class ReCaptcha implements Adapter * ReCaptcha Adapter * * See more information about the implementation instructions + * * @see https://developers.google.com/recaptcha/docs/verify * * Admin Panel * @see https://www.google.com/recaptcha/admin * - * @param string $secret - * @param string $response - * @param string $remoteIP + * @param string $secret + * @param string $response + * @param string $remoteIP */ public function __construct(string $secret, string $response, string $remoteIP) { - $this->secret = $secret; + $this->secret = $secret; $this->response = $response; $this->remoteIP = $remoteIP; } @@ -54,33 +55,33 @@ public function __construct(string $secret, string $response, string $remoteIP) * * Check if user is human or not, compared to score * - * @param float $score + * @param float $score * @return bool */ - public function check(float $score = 0.5):bool + public function check(float $score = 0.5): bool { - $url = 'https://www.google.com/recaptcha/api/siteverify'; - $fields = array( - 'secret' => \urlencode($this->secret), - 'response' => \urlencode($this->response), - 'remoteip' => \urlencode($this->remoteIP), - ); + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = [ + 'secret' => \urlencode($this->secret), + 'response' => \urlencode($this->response), + 'remoteip' => \urlencode($this->remoteIP), + ]; //open connection $ch = \curl_init(); //set the url, number of POST vars, POST data - \curl_setopt($ch,CURLOPT_URL, $url); - \curl_setopt($ch,CURLOPT_POST, \count($fields)); - \curl_setopt($ch,CURLOPT_POSTFIELDS, \http_build_query($fields)); + \curl_setopt($ch, CURLOPT_URL, $url); + \curl_setopt($ch, CURLOPT_POST, \count($fields)); + \curl_setopt($ch, CURLOPT_POSTFIELDS, \http_build_query($fields)); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //execute post - $result = \json_decode((string)\curl_exec($ch), true); + $result = \json_decode((string) \curl_exec($ch), true); //close connection \curl_close($ch); - if ($result['success'] && $result['score'] >= $score ) { + if ($result['success'] && $result['score'] >= $score) { return true; } else { return false; @@ -90,11 +91,12 @@ public function check(float $score = 0.5):bool /** * Delete logs older than $datetime * - * @param string $datetime + * @param string $datetime * @return bool + * * @throws Exception */ - public function cleanup(string $datetime):bool + public function cleanup(string $datetime): bool { throw new Exception('Method not supported'); } @@ -104,14 +106,14 @@ public function cleanup(string $datetime):bool * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * + * @param int $offset + * @param int $limit * @return array + * * @throws Exception */ - public function getLogs(int $offset, int $limit): array - { + public function getLogs(int $offset, int $limit): array + { throw new Exception('Method not supported'); } } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index a0beaa1d266..584a2f0a9b2 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -16,7 +16,7 @@ class TimeLimit implements Adapter { - const COLLECTION = "abuse"; + const COLLECTION = 'abuse'; /** * @var Database @@ -49,15 +49,15 @@ class TimeLimit implements Adapter protected array $params = []; /** - * @param string $key - * @param int $seconds - * @param int $limit - * @param Database $db + * @param string $key + * @param int $seconds + * @param int $limit + * @param Database $db */ public function __construct(string $key, int $limit, int $seconds, Database $db) { $this->key = $key; - $time = (int)\date('U', (int)(\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); $this->limit = $limit; $this->db = $db; @@ -70,8 +70,8 @@ public function __construct(string $key, int $limit, int $seconds, Database $db) */ public function setup(): void { - if (!$this->db->exists($this->db->getDefaultDatabase())) { - throw new Exception("You need to create database before running timelimit setup"); + if (! $this->db->exists($this->db->getDefaultDatabase())) { + throw new Exception('You need to create database before running timelimit setup'); } $attributes = [ @@ -129,9 +129,8 @@ public function setup(): void * * Set custom param for key pattern parsing * - * @param string $key - * @param string $value - * + * @param string $key + * @param string $value * @return $this */ public function setParam(string $key, string $value): self @@ -172,9 +171,10 @@ protected function parseKey(): string * * Checks if number of counts is bigger or smaller than current limit * - * @param string $key - * @param string $datetime + * @param string $key + * @param string $datetime * @return int + * * @throws \Exception */ protected function count(string $key, string $datetime): int @@ -183,7 +183,7 @@ protected function count(string $key, string $datetime): int return 0; } - if (!\is_null($this->count)) { // Get fetched result + if (! \is_null($this->count)) { // Get fetched result return $this->count; } @@ -200,16 +200,16 @@ protected function count(string $key, string $datetime): int $result = 0; } - $this->count = (int)$result; + $this->count = (int) $result; return $this->count; } /** - * @param string $key - * @param string $datetime - * + * @param string $key + * @param string $datetime * @return void + * * @throws AuthorizationException|Structure|\Exception */ protected function hit(string $key, string $datetime): void @@ -247,10 +247,10 @@ protected function hit(string $key, string $datetime): void * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * + * @param int $offset + * @param int $limit * @return array + * * @throws \Exception */ public function getLogs(int $offset, int $limit): array @@ -263,9 +263,9 @@ public function getLogs(int $offset, int $limit): array /** * Delete logs older than $timestamp seconds * - * @param string $datetime - * + * @param string $datetime * @return bool + * * @throws AuthorizationException|\Exception */ public function cleanup(string $datetime): bool @@ -279,8 +279,7 @@ public function cleanup(string $datetime): bool foreach ($documents as $document) { $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); } - } while (!empty($documents)); - + } while (! empty($documents)); }); return true; @@ -292,6 +291,7 @@ public function cleanup(string $datetime): bool * Checks if number of counts is bigger or smaller than current limit. limit 0 is equal to unlimited * * @return bool + * * @throws \Exception */ public function check(): bool @@ -304,6 +304,7 @@ public function check(): bool if ($this->limit > $this->count($key, $this->time)) { $this->hit($key, $this->time); + return false; } @@ -316,12 +317,13 @@ public function check(): bool * Returns the number of current remaining counts * * @return int - * @throws \Exception * + * @throws \Exception */ public function remaining(): int { $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done + return (0 > $left) ? 0 : $left; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 763675bb3cb..9ba5c685aec 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -2,12 +2,13 @@ /** * Utopia PHP Framework * - * @package Abuse - * @subpackage Tests * * @link https://github.com/utopia-php/framework + * * @author Eldad Fux + * * @version 1.0 RC4 + * * @license The MIT License (MIT) */ @@ -46,7 +47,7 @@ public function setUp(): void $db->setNamespace('namespace'); $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); - if(!$db->exists('utopiaTests')) { + if (! $db->exists('utopiaTests')) { $db->create('utopiaTests'); $adapter->setup(); } @@ -72,7 +73,6 @@ public function testIsValid() public function testCleanup() { - // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(1, \count($logs)); From 5c636978485c9ca7545d41446bc6efc16a3b643c Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 16 Jan 2023 09:36:39 +0200 Subject: [PATCH 156/470] upgrade 0.29 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9f4edb4081e..66e28f543fb 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.28.*" + "utopia-php/database": "0.29.*" }, "require-dev": { "phpunit/phpunit": "^9.4", From b9c6353fb40f8a5d483ba1b242b0993aea5934cc Mon Sep 17 00:00:00 2001 From: fogelito Date: Mon, 16 Jan 2023 09:42:07 +0200 Subject: [PATCH 157/470] composer.lock --- composer.lock | 780 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 516 insertions(+), 264 deletions(-) diff --git a/composer.lock b/composer.lock index 4991d5a4690..e58ba1c1896 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,287 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a62cb0b4e78a919c5e3ede9f1d0eb8f1", + "content-hash": "a79252f27730fa740dee27166b6e6d9a", "packages": [ + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", + "reference": "1e0104b46f045868f11942aea058cd7186d6c303", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8.0", + "php": "^7.0|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0|^8.5|^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" + }, + "time": "2021-02-04T16:20:16+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", + "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.8.1", + "jean85/pretty-package-versions": "^1.2", + "php": "^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.19" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5, <3.5.5", + "symfony/phpunit-bridge": "5.x-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + }, + "time": "2020-11-25T12:26:02+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "utopia-php/cache", "version": "0.8.0", @@ -57,22 +336,23 @@ }, { "name": "utopia-php/database", - "version": "0.28.0", + "version": "0.29.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94" + "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ef6506af1c09c22f5dc1e7859159d323f7fafa94", - "reference": "ef6506af1c09c22f5dc1e7859159d323f7fafa94", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d4449bca6a6ca620588df6575beff35c7e8dcadd", + "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd", "shasum": "" }, "require": { "php": ">=8.0", "utopia-php/cache": "0.8.*", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.*.*", + "utopia-php/mongo": "0.0.2" }, "require-dev": { "ext-mongodb": "*", @@ -105,30 +385,31 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.28.0" + "source": "https://github.com/utopia-php/database/tree/0.29.0" }, - "time": "2022-10-31T09:58:46+00:00" + "time": "2023-01-15T23:51:06+00:00" }, { "name": "utopia-php/framework", - "version": "0.23.4", + "version": "0.26.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b" + "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/97f64aa1732af92b967c3576f16967dc762ad47b", - "reference": "97f64aa1732af92b967c3576f16967dc762ad47b", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e", + "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e", "shasum": "" }, "require": { "php": ">=8.0.0" }, "require-dev": { + "laravel/pint": "^1.2", "phpunit/phpunit": "^9.5.25", - "vimeo/psalm": "^4.27.0" + "vimeo/psalm": "4.27.0" }, "type": "library", "autoload": { @@ -148,9 +429,69 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.23.4" + "source": "https://github.com/utopia-php/framework/tree/0.26.0" + }, + "time": "2023-01-13T08:14:43+00:00" + }, + { + "name": "utopia-php/mongo", + "version": "0.0.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/mongo.git", + "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", + "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", + "shasum": "" }, - "time": "2022-10-31T11:57:14+00:00" + "require": { + "ext-mongodb": "*", + "mongodb/mongodb": "1.8.0", + "php": ">=8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.14", + "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.8.*", + "phpunit/phpunit": "^9.4", + "swoole/ide-helper": "4.8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Mongo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Wess", + "email": "wess@appwrite.io" + } + ], + "description": "A simple library to manage Mongo database", + "keywords": [ + "database", + "mongo", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/mongo/issues", + "source": "https://github.com/utopia-php/mongo/tree/0.0.2" + }, + "time": "2022-11-08T11:58:46+00:00" } ], "packages-dev": [ @@ -320,79 +661,6 @@ ], "time": "2021-03-30T17:13:30+00:00" }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, { "name": "composer/semver", "version": "3.3.2", @@ -577,30 +845,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -627,7 +895,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -643,7 +911,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -746,6 +1014,72 @@ }, "time": "2022-03-02T22:36:06+00:00" }, + { + "name": "laravel/pint", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1", + "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.11.0", + "illuminate/view": "^9.32.0", + "laravel-zero/framework": "^9.2.0", + "mockery/mockery": "^1.5.1", + "nunomaduro/larastan": "^2.2.0", + "nunomaduro/termwind": "^1.14.0", + "pestphp/pest": "^1.22.1" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2022-11-29T16:25:20+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.0", @@ -858,16 +1192,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v4.15.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", "shasum": "" }, "require": { @@ -908,9 +1242,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2022-11-12T15:38:23+00:00" }, { "name": "openlss/lib-array2xml", @@ -1243,16 +1577,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.18", + "version": "9.2.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a" + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/12fddc491826940cf9b7e88ad9664cf51f0f6d0a", - "reference": "12fddc491826940cf9b7e88ad9664cf51f0f6d0a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", "shasum": "" }, "require": { @@ -1308,7 +1642,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.18" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" }, "funding": [ { @@ -1316,7 +1650,7 @@ "type": "github" } ], - "time": "2022-10-27T13:35:33+00:00" + "time": "2022-12-28T12:41:10+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1561,20 +1895,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.26", + "version": "9.5.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1643,7 +1977,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" }, "funding": [ { @@ -1659,7 +1993,7 @@ "type": "tidelift" } ], - "time": "2022-10-28T06:00:21+00:00" + "time": "2023-01-14T12:32:24+00:00" }, { "name": "psr/container", @@ -2730,16 +3064,16 @@ }, { "name": "symfony/console", - "version": "v5.4.15", + "version": "v5.4.17", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669" + "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ea59bb0edfaf9f28d18d8791410ee0355f317669", - "reference": "ea59bb0edfaf9f28d18d8791410ee0355f317669", + "url": "https://api.github.com/repos/symfony/console/zipball/58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", + "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", "shasum": "" }, "require": { @@ -2809,7 +3143,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.15" + "source": "https://github.com/symfony/console/tree/v5.4.17" }, "funding": [ { @@ -2825,20 +3159,20 @@ "type": "tidelift" } ], - "time": "2022-10-26T21:41:52+00:00" + "time": "2022-12-28T14:15:31+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", "shasum": "" }, "require": { @@ -2847,7 +3181,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -2876,7 +3210,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" }, "funding": [ { @@ -2892,20 +3226,20 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { @@ -2920,7 +3254,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2958,7 +3292,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" }, "funding": [ { @@ -2974,20 +3308,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", "shasum": "" }, "require": { @@ -2999,7 +3333,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3039,7 +3373,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" }, "funding": [ { @@ -3055,20 +3389,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "shasum": "" }, "require": { @@ -3080,7 +3414,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3123,7 +3457,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" }, "funding": [ { @@ -3139,20 +3473,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -3167,7 +3501,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3206,7 +3540,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -3222,20 +3556,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", "shasum": "" }, "require": { @@ -3244,7 +3578,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3285,90 +3619,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-24T11:49:31+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" }, "funding": [ { @@ -3384,20 +3635,20 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", "shasum": "" }, "require": { @@ -3413,7 +3664,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -3453,7 +3704,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" }, "funding": [ { @@ -3469,20 +3720,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:18:58+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/string", - "version": "v6.1.7", + "version": "v6.2.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "823f143370880efcbdfa2dbca946b3358c4707e5" + "reference": "863219fd713fa41cbcd285a79723f94672faff4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/823f143370880efcbdfa2dbca946b3358c4707e5", - "reference": "823f143370880efcbdfa2dbca946b3358c4707e5", + "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", + "reference": "863219fd713fa41cbcd285a79723f94672faff4d", "shasum": "" }, "require": { @@ -3498,6 +3749,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, @@ -3538,7 +3790,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.7" + "source": "https://github.com/symfony/string/tree/v6.2.2" }, "funding": [ { @@ -3554,7 +3806,7 @@ "type": "tidelift" } ], - "time": "2022-10-10T09:34:31+00:00" + "time": "2022-12-14T16:11:27+00:00" }, { "name": "theseer/tokenizer", From 1ef5218c30a9a9291e858ad61dae9b551e7c7655 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 14:12:48 +0000 Subject: [PATCH 158/470] chore: review comments --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 9d8c460b910..b4ae46c3207 100755 --- a/composer.json +++ b/composer.json @@ -5,6 +5,12 @@ "keywords": ["php","framework", "upf", "utopia", "abuse"], "license": "MIT", "minimum-stability": "stable", + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + } + ], "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, From 971b4af78dc573659f6a581810b3209ee60bfd8e Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 14:14:34 +0000 Subject: [PATCH 159/470] chore: update deps --- composer.lock | 559 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 533 insertions(+), 26 deletions(-) diff --git a/composer.lock b/composer.lock index e07e3ac093c..91f16a4434a 100644 --- a/composer.lock +++ b/composer.lock @@ -48,6 +48,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", @@ -105,6 +115,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], "description": "A wrapper for ocramius/package-versions to get pretty versions strings", "keywords": [ "composer", @@ -162,6 +178,16 @@ "license": [ "Apache-2.0" ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], "description": "MongoDB driver library", "homepage": "https://jira.mongodb.org/browse/PHPLIB", "keywords": [ @@ -217,7 +243,21 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" - ], + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ @@ -500,6 +540,24 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], "description": "A non-blocking concurrency framework for PHP applications.", "homepage": "https://amphp.org/amp", "keywords": [ @@ -570,6 +628,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], "description": "A stream abstraction to make working with non-blocking I/O simple.", "homepage": "http://amphp.org/byte-stream", "keywords": [ @@ -629,6 +697,23 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", @@ -689,6 +774,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], "description": "Restarts a process without Xdebug.", "keywords": [ "Xdebug", @@ -789,6 +880,13 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ @@ -847,6 +945,12 @@ "license": [ "ISC" ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], "description": "A more advanced JSONRPC implementation", "support": { "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", @@ -891,6 +995,12 @@ "license": [ "ISC" ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], "description": "PHP classes for the Language Server Protocol", "keywords": [ "language", @@ -1064,6 +1174,14 @@ "license": [ "OSL-3.0" ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], "description": "Map nested JSON structures onto PHP classes", "support": { "email": "cweiske@cweiske.de", @@ -1074,16 +1192,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -1112,6 +1230,11 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Nikita Popov" + } + ], "description": "A PHP parser written in PHP", "keywords": [ "parser", @@ -1119,9 +1242,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "openlss/lib-array2xml", @@ -1150,6 +1273,18 @@ "license": [ "Apache-2.0" ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], "description": "Array2XML conversion library credit to lalit.org", "homepage": "https://www.nullivex.com", "keywords": [ @@ -1200,6 +1335,23 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", @@ -1234,6 +1386,23 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", @@ -1273,6 +1442,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], "description": "Common reflection classes used by phpdocumentor to reflect the code structure", "homepage": "http://www.phpdoc.org", "keywords": [ @@ -1328,6 +1503,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", @@ -1377,6 +1562,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", @@ -1386,16 +1577,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.23", + "version": "9.2.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", "shasum": "" }, "require": { @@ -1435,6 +1626,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ @@ -1444,7 +1642,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" }, "funding": [ { @@ -1452,7 +1650,7 @@ "type": "github" } ], - "time": "2022-12-28T12:41:10+00:00" + "time": "2023-01-26T08:26:55+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1489,6 +1687,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ @@ -1546,6 +1751,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Invoke callables with a timeout", "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ @@ -1598,6 +1810,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Simple template engine.", "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ @@ -1650,6 +1869,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Utility class for timing", "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ @@ -1735,6 +1961,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", "keywords": [ @@ -1794,6 +2027,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], "description": "Common Container Interface (PHP FIG PSR-11)", "homepage": "https://github.com/php-fig/container", "keywords": [ @@ -1841,6 +2080,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", "keywords": [ @@ -1888,6 +2133,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Library for parsing CLI options", "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { @@ -1937,6 +2189,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { @@ -1986,6 +2245,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { @@ -2037,6 +2302,24 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], "description": "Provides the functionality to compare PHP values for equality", "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ @@ -2092,6 +2375,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Library for calculating the complexity of PHP code units", "homepage": "https://github.com/sebastianbergmann/complexity", "support": { @@ -2142,6 +2432,16 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ @@ -2200,6 +2500,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Provides functionality to handle HHVM/PHP environments", "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ @@ -2256,6 +2562,28 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], "description": "Provides the functionality to export PHP variables for visualization", "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ @@ -2315,6 +2643,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Snapshotting of global state", "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ @@ -2368,6 +2702,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Library for counting the lines of code in PHP source code", "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { @@ -2419,6 +2760,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { @@ -2468,6 +2815,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { @@ -2517,6 +2870,20 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { @@ -2566,6 +2933,12 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { @@ -2615,6 +2988,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", "support": { @@ -2661,6 +3041,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", "support": { @@ -2677,16 +3064,16 @@ }, { "name": "symfony/console", - "version": "v5.4.17", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f" + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", - "reference": "58422fdcb0e715ed05b385f70d3e8b5ed4bbd45f", + "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", "shasum": "" }, "require": { @@ -2737,6 +3124,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ @@ -2746,7 +3143,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.17" + "source": "https://github.com/symfony/console/tree/v5.4.19" }, "funding": [ { @@ -2762,7 +3159,7 @@ "type": "tidelift" } ], - "time": "2022-12-28T14:15:31+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2800,6 +3197,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { @@ -2866,6 +3273,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ @@ -2935,6 +3352,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ @@ -3009,6 +3436,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ @@ -3083,6 +3520,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ @@ -3153,6 +3600,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ @@ -3226,6 +3683,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ @@ -3257,16 +3724,16 @@ }, { "name": "symfony/string", - "version": "v6.2.2", + "version": "v6.2.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "863219fd713fa41cbcd285a79723f94672faff4d" + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", - "reference": "863219fd713fa41cbcd285a79723f94672faff4d", + "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", "shasum": "" }, "require": { @@ -3302,6 +3769,16 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", "keywords": [ @@ -3313,7 +3790,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.2" + "source": "https://github.com/symfony/string/tree/v6.2.5" }, "funding": [ { @@ -3329,7 +3806,7 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:11:27+00:00" + "time": "2023-01-01T08:38:09+00:00" }, { "name": "theseer/tokenizer", @@ -3361,6 +3838,13 @@ "license": [ "BSD-3-Clause" ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", @@ -3462,6 +3946,11 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Matthew Brown" + } + ], "description": "A static analysis tool for finding errors in PHP applications", "keywords": [ "code", @@ -3509,6 +3998,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ "assert", @@ -3557,6 +4052,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], "description": "A PHP implementation of Ant's glob.", "support": { "issues": "https://github.com/webmozarts/glob/issues", @@ -3601,6 +4102,12 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", "support": { "issues": "https://github.com/webmozart/path-util/issues", @@ -3621,5 +4128,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From 91b7cb74c5ee5fc6164ee25c81557bdd4162052d Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 14:23:42 +0000 Subject: [PATCH 160/470] feat: remove authors --- composer.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/composer.json b/composer.json index 66e28f543fb..a98fe547675 100755 --- a/composer.json +++ b/composer.json @@ -5,12 +5,6 @@ "keywords": ["php","framework", "upf", "utopia", "abuse"], "license": "MIT", "minimum-stability": "stable", - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, From 002bb5a1a1f764f8dc2889f30479541ee9140109 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 14:49:03 +0000 Subject: [PATCH 161/470] feat: fix php stan errors --- composer.lock | 310 ++++++++++++++++++------------- src/Abuse/Abuse.php | 2 +- src/Abuse/Adapter.php | 2 +- src/Abuse/Adapters/ReCaptcha.php | 5 +- src/Abuse/Adapters/TimeLimit.php | 17 +- tests/Abuse/AbuseTest.php | 6 +- 6 files changed, 203 insertions(+), 139 deletions(-) diff --git a/composer.lock b/composer.lock index 58209e0fa64..c210d969d8f 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": "c141dfb7f633587b3179a7604b93c205", + "content-hash": "2f1267c9905b8e3f4e2d0dcc635931db", "packages": [ { "name": "composer/package-versions-deprecated", @@ -204,16 +204,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -222,7 +222,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -267,7 +267,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -283,7 +283,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "utopia-php/cache", @@ -404,24 +404,25 @@ }, { "name": "utopia-php/framework", - "version": "0.22.1", + "version": "0.26.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df" + "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", - "reference": "9f35d36ed4b8fa1c92962c77ef02b49c2f5919df", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e", + "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e", "shasum": "" }, "require": { "php": ">=8.0.0" }, "require-dev": { + "laravel/pint": "^1.2", "phpunit/phpunit": "^9.5.25", - "vimeo/psalm": "^4.27.0" + "vimeo/psalm": "4.27.0" }, "type": "library", "autoload": { @@ -433,12 +434,6 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - } - ], "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", @@ -447,9 +442,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.22.1" + "source": "https://github.com/utopia-php/framework/tree/0.26.0" }, - "time": "2022-10-07T14:51:40+00:00" + "time": "2023-01-13T08:14:43+00:00" } ], "packages-dev": [ @@ -803,30 +798,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -853,7 +848,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -869,7 +864,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -1084,16 +1079,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -1134,9 +1129,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "openlss/lib-array2xml", @@ -1414,25 +1409,30 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -1458,22 +1458,81 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" + }, + "time": "2022-10-14T12:47:21+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.9.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "81b8cc491a8900946b732d922a0e581b2913a359" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/81b8cc491a8900946b732d922a0e581b2913a359", + "reference": "81b8cc491a8900946b732d922a0e581b2913a359", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.9.x" }, - "time": "2022-03-15T21:29:03+00:00" + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-01-26T12:21:06+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "9.2.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", "shasum": "" }, "require": { @@ -1529,7 +1588,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" }, "funding": [ { @@ -1537,7 +1596,7 @@ "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2023-01-26T08:26:55+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1782,20 +1841,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.5.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1864,7 +1923,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" }, "funding": [ { @@ -1880,7 +1939,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2023-01-14T12:32:24+00:00" }, { "name": "psr/container", @@ -2951,16 +3010,16 @@ }, { "name": "symfony/console", - "version": "v5.4.14", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d" + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/984ea2c0f45f42dfed01d2f3987b187467c4b16d", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d", + "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", "shasum": "" }, "require": { @@ -3030,7 +3089,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.14" + "source": "https://github.com/symfony/console/tree/v5.4.19" }, "funding": [ { @@ -3046,20 +3105,20 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:01:20+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", "shasum": "" }, "require": { @@ -3068,7 +3127,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -3097,7 +3156,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" }, "funding": [ { @@ -3113,20 +3172,20 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { @@ -3141,7 +3200,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3179,7 +3238,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" }, "funding": [ { @@ -3195,20 +3254,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", "shasum": "" }, "require": { @@ -3220,7 +3279,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3260,7 +3319,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" }, "funding": [ { @@ -3276,20 +3335,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "shasum": "" }, "require": { @@ -3301,7 +3360,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3344,7 +3403,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" }, "funding": [ { @@ -3360,20 +3419,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -3388,7 +3447,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3427,7 +3486,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -3443,20 +3502,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", "shasum": "" }, "require": { @@ -3465,7 +3524,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3506,7 +3565,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" }, "funding": [ { @@ -3522,20 +3581,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", "shasum": "" }, "require": { @@ -3551,7 +3610,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -3591,7 +3650,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" }, "funding": [ { @@ -3607,20 +3666,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:18:58+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/string", - "version": "v6.1.6", + "version": "v6.2.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864" + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7e7e0ff180d4c5a6636eaad57b65092014b61864", - "reference": "7e7e0ff180d4c5a6636eaad57b65092014b61864", + "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", "shasum": "" }, "require": { @@ -3636,6 +3695,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, @@ -3676,7 +3736,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.6" + "source": "https://github.com/symfony/string/tree/v6.2.5" }, "funding": [ { @@ -3692,7 +3752,7 @@ "type": "tidelift" } ], - "time": "2022-10-10T09:34:31+00:00" + "time": "2023-01-01T08:38:09+00:00" }, { "name": "theseer/tokenizer", @@ -4005,7 +4065,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "phpstan/phpstan": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4014,5 +4076,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 10565870942..bc35ac994b2 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -37,7 +37,7 @@ public function check(): bool * @param int $offset * @param int $limit * - * @return array + * @return array */ public function getLogs(int $offset, int $limit): array { diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 9ea49d1a7af..0c74f81b249 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -21,7 +21,7 @@ public function check(): bool; * @param int $offset * @param int $limit * - * @return array + * @return array */ public function getLogs(int $offset, int $limit): array; diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 8f2e4340ede..4272a58147a 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -57,7 +57,7 @@ public function __construct(string $secret, string $response, string $remoteIP) * @param float $score * @return bool */ - public function check(float $score = 0.5):bool + public function check(float $score = 0.5): bool { $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array( @@ -76,6 +76,7 @@ public function check(float $score = 0.5):bool \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //execute post + /** @var array $result */ $result = \json_decode((string)\curl_exec($ch), true); //close connection @@ -107,7 +108,7 @@ public function cleanup(string $datetime):bool * @param int $offset * @param int $limit * - * @return array + * @return array * @throws Exception */ public function getLogs(int $offset, int $limit): array diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index a0beaa1d266..494da4892c4 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -44,7 +44,7 @@ class TimeLimit implements Adapter protected ?int $count = null; /** - * @var array + * @var array */ protected array $params = []; @@ -146,7 +146,7 @@ public function setParam(string $key, string $value): self * * Return array of all key params * - * @return array + * @return array */ protected function getParams(): array { @@ -187,6 +187,7 @@ protected function count(string $key, string $datetime): int return $this->count; } + /** @var array $result */ $result = Authorization::skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), @@ -194,14 +195,11 @@ protected function count(string $key, string $datetime): int ]); }); + $this->count = 0; if (\count($result) === 1) { - $result = $result[0]->getAttribute('count', 0); - } else { - $result = 0; + $this->count = intval($result[0]->getAttribute('count', 0)); } - $this->count = (int)$result; - return $this->count; } @@ -234,6 +232,7 @@ protected function hit(string $key, string $datetime): void ]; $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } else { + /** @var Document $data */ $data->setAttribute('count', $data->getAttribute('count', 0) + 1); $this->db->updateDocument(TimeLimit::COLLECTION, $data->getId(), $data); } @@ -250,11 +249,12 @@ protected function hit(string $key, string $datetime): void * @param int $offset * @param int $limit * - * @return array + * @return array * @throws \Exception */ public function getLogs(int $offset, int $limit): array { + /** @phpstan-ignore-next-line */ return Authorization::skip(function () use ($offset, $limit) { return $this->db->find(TimeLimit::COLLECTION, [Query::limit($limit), Query::offset($offset), Query::orderDesc('')]); }); @@ -276,6 +276,7 @@ public function cleanup(string $datetime): bool Query::lessThan('time', $datetime), ]); + /** @var array $document */ foreach ($documents as $document) { $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 763675bb3cb..9dceb83f223 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -58,10 +58,10 @@ public function setUp(): void public function tearDown(): void { - $this->abuse = null; + unset($this->abuse); } - public function testIsValid() + public function testIsValid(): void { // Use vars to resolve adapter key $this->assertEquals($this->abuse->check(), false); @@ -70,7 +70,7 @@ public function testIsValid() $this->assertEquals($this->abuse->check(), true); } - public function testCleanup() + public function testCleanup(): void { // Check that there is only one log From a3ffecfbc130a9fb4b54174fd3a66c791ced9f9f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 21:18:45 +0000 Subject: [PATCH 162/470] chore: linter --- composer.lock | 61 ++++++++++++++++++++++++++++++++++++++++++- src/Abuse/Adapter.php | 8 +++--- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 8fd7d7c0416..41dfbf5ae18 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": "a79252f27730fa740dee27166b6e6d9a", + "content-hash": "8ae8242b12b26ac420f9385b7e1d0379", "packages": [ { "name": "composer/package-versions-deprecated", @@ -1575,6 +1575,65 @@ }, "time": "2022-10-14T12:47:21+00:00" }, + { + "name": "phpstan/phpstan", + "version": "1.9.x-dev", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9334ff3ded6489b78dc95161a1fbab5bed64b7c6", + "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.9.x" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-01-27T20:41:39+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.24", diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index e42224285ae..e57bda07de4 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -19,13 +19,11 @@ public function check(): bool; * Return logs with an offset and limit * <<<<<<< HEAD - * @param int $offset - * @param int $limit - * - * @return array -======= * @param int $offset * @param int $limit + * @param int $offset + * @param int $limit + * @return array * @return array >>>>>>> b27dfba4584aefab7e5434eadfb0befd2de2066c */ From 011f28a1df65be70fe74bc777fd48c751d2bd147 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 27 Jan 2023 21:24:34 +0000 Subject: [PATCH 163/470] chore: phpstan --- composer.json | 1 + src/Abuse/Abuse.php | 2 +- src/Abuse/Adapter.php | 3 --- src/Abuse/Adapters/ReCaptcha.php | 3 ++- src/Abuse/Adapters/TimeLimit.php | 2 +- tests/Abuse/AbuseTest.php | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 686db18cb28..35680123e13 100755 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, "scripts": { + "check": "./vendor/bin/phpstan analyse --level max src tests", "lint": "./vendor/bin/pint --test", "format": "./vendor/bin/pint" }, diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 632bb1967b5..8e66211f77b 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -36,7 +36,7 @@ public function check(): bool * * @param int $offset * @param int $limit - * @return array + * @return array */ public function getLogs(int $offset, int $limit): array { diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index e57bda07de4..027ad60c42e 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -18,14 +18,11 @@ public function check(): bool; * * Return logs with an offset and limit * -<<<<<<< HEAD * @param int $offset * @param int $limit * @param int $offset * @param int $limit * @return array - * @return array ->>>>>>> b27dfba4584aefab7e5434eadfb0befd2de2066c */ public function getLogs(int $offset, int $limit): array; diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 7d9562868a8..ac765ff04ae 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -77,6 +77,7 @@ public function check(float $score = 0.5): bool \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //execute post + /** @var array $result */ $result = \json_decode((string) \curl_exec($ch), true); //close connection @@ -108,7 +109,7 @@ public function cleanup(string $datetime): bool * * @param int $offset * @param int $limit - * @return array + * @return array * * @throws Exception */ diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 8acc5c4b401..8fdadf686b9 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -250,7 +250,7 @@ protected function hit(string $key, string $datetime): void * * @param int $offset * @param int $limit - * @return array + * @return array * * @throws \Exception */ diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 7e4fd2a9c13..48991df758e 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -36,7 +36,7 @@ public function setUp(): void $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); if (! $db->exists('utopiaTests')) { - $db->create('utopiaTests'); + $db->create(); $adapter->setup(); } From a6b5a6e6ed57671f4b9b6ef65f3085333b17406d Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Feb 2023 10:29:15 +0200 Subject: [PATCH 164/470] composer.json --- composer.json | 2 +- composer.lock | 91 +++++++++++++++++++++++++-------------------------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index 35680123e13..f03be2dfd97 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.29.*" + "utopia-php/database": "0.30.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 41dfbf5ae18..df984ce96f8 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": "8ae8242b12b26ac420f9385b7e1d0379", + "content-hash": "34919b745bc4898bd9748cb49426c534", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.29.0", + "version": "0.30.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd" + "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d4449bca6a6ca620588df6575beff35c7e8dcadd", - "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65", + "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65", "shasum": "" }, "require": { @@ -356,13 +356,12 @@ }, "require-dev": { "ext-mongodb": "*", - "ext-pdo": "*", "ext-redis": "*", "fakerphp/faker": "^1.14", "mongodb/mongodb": "1.8.0", "phpunit/phpunit": "^9.4", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.11.0", + "utopia-php/cli": "^0.14.0", "vimeo/psalm": "4.0.1" }, "type": "library", @@ -385,22 +384,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.29.0" + "source": "https://github.com/utopia-php/database/tree/0.30.1" }, - "time": "2023-01-15T23:51:06+00:00" + "time": "2023-02-14T06:25:03+00:00" }, { "name": "utopia-php/framework", - "version": "0.26.0", + "version": "0.27.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e" + "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/b8d0447f5c98291d7759db05460ecced29a0f9ee", + "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee", "shasum": "" }, "require": { @@ -429,9 +428,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.26.0" + "source": "https://github.com/utopia-php/framework/tree/0.27.0" }, - "time": "2023-01-13T08:14:43+00:00" + "time": "2023-01-29T05:36:17+00:00" }, { "name": "utopia-php/mongo", @@ -1581,12 +1580,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6" + "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9334ff3ded6489b78dc95161a1fbab5bed64b7c6", - "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cf2107d2c96e9653f6267a15e29e38fb4773e0a5", + "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5", "shasum": "" }, "require": { @@ -1632,7 +1631,7 @@ "type": "tidelift" } ], - "time": "2023-01-27T20:41:39+00:00" + "time": "2023-02-12T08:37:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1954,16 +1953,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.28", + "version": "9.6.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", "shasum": "" }, "require": { @@ -2005,7 +2004,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -2036,7 +2035,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" }, "funding": [ { @@ -2052,7 +2051,7 @@ "type": "tidelift" } ], - "time": "2023-01-14T12:32:24+00:00" + "time": "2023-02-04T13:37:15+00:00" }, { "name": "psr/container", @@ -2523,16 +2522,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -2574,7 +2573,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -2582,7 +2581,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -2896,16 +2895,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -2944,10 +2943,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -2955,7 +2954,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -3014,16 +3013,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -3058,7 +3057,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -3066,7 +3065,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -4189,5 +4188,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } From 83027ca14600fcff855579aaa204c5b8300e136e Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Feb 2023 10:42:12 +0200 Subject: [PATCH 165/470] composer format changes --- src/Abuse/Adapters/TimeLimit.php | 11 +++++------ tests/Abuse/AbuseTest.php | 5 ++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 538dcc11687..80b00557745 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -234,23 +234,22 @@ protected function hit(string $key, string $datetime): void try { $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); - } catch (Duplicate $e){ + } catch (Duplicate $e) { // Duplicate in case of race condition $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - if($data){ + if ($data) { $this->count = $data->getAttribute('count'); - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } else { - Throw new \Exception('Document Not Found'); + throw new \Exception('Document Not Found'); } } - } else { - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(),'count'); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } }); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 28acfc4ded5..57cd6a204ce 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -18,6 +18,7 @@ class AbuseTest extends TestCase { protected ?Abuse $abuse = null; + protected ?Abuse $abuseRace = null; /** @@ -67,7 +68,7 @@ public function testIsValid(): void public function testAbuse() { - for($i = 0 ; $i < 999 ; $i++){ + for ($i = 0; $i < 999; $i++) { $this->assertEquals($this->abuseRace->check(), false); } @@ -89,6 +90,4 @@ public function testCleanup(): void $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(0, \count($logs)); } - - } From 364341384b97ba7c350ce1ad7da6e675b24ba124 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Feb 2023 11:14:41 +0200 Subject: [PATCH 166/470] composer check fixes --- src/Abuse/Adapters/TimeLimit.php | 6 ++++-- tests/Abuse/AbuseTest.php | 8 +++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 80b00557745..403f6a8adb5 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -236,19 +236,21 @@ protected function hit(string $key, string $datetime): void $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } catch (Duplicate $e) { // Duplicate in case of race condition + /** @var Document $data */ $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - if ($data) { - $this->count = $data->getAttribute('count'); + if ($data != false) { + $this->count = intval($data->getAttribute('count')); $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } else { throw new \Exception('Document Not Found'); } } } else { + /** @var Document $data */ $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } }); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 57cd6a204ce..a5a895565e1 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -17,12 +17,10 @@ class AbuseTest extends TestCase { - protected ?Abuse $abuse = null; - - protected ?Abuse $abuseRace = null; + protected Abuse $abuse; + protected Abuse $abuseRace; /** - * @throws Limit * @throws Exception * @throws \Exception */ @@ -66,7 +64,7 @@ public function testIsValid(): void $this->assertEquals($this->abuse->check(), true); } - public function testAbuse() + public function testAbuse(): void { for ($i = 0; $i < 999; $i++) { $this->assertEquals($this->abuseRace->check(), false); From e8300a28b24d2d6fda4af824c4d04ba303d9e373 Mon Sep 17 00:00:00 2001 From: fogelito Date: Tue, 14 Feb 2023 11:17:29 +0200 Subject: [PATCH 167/470] remove namespace --- tests/Abuse/AbuseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index a5a895565e1..21f94012ca9 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -12,12 +12,12 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Database\Exception\Limit; use Utopia\Exception; class AbuseTest extends TestCase { protected Abuse $abuse; + protected Abuse $abuseRace; /** From aa676fabace4ed02cb21e8541bfb382ff8111ba9 Mon Sep 17 00:00:00 2001 From: shimon Date: Tue, 14 Feb 2023 11:21:58 +0200 Subject: [PATCH 168/470] version change --- composer.json | 2 +- composer.lock | 91 +++++++++++++++++++++++++-------------------------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index 35680123e13..f03be2dfd97 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.29.*" + "utopia-php/database": "0.30.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 41dfbf5ae18..df984ce96f8 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": "8ae8242b12b26ac420f9385b7e1d0379", + "content-hash": "34919b745bc4898bd9748cb49426c534", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.29.0", + "version": "0.30.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd" + "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d4449bca6a6ca620588df6575beff35c7e8dcadd", - "reference": "d4449bca6a6ca620588df6575beff35c7e8dcadd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65", + "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65", "shasum": "" }, "require": { @@ -356,13 +356,12 @@ }, "require-dev": { "ext-mongodb": "*", - "ext-pdo": "*", "ext-redis": "*", "fakerphp/faker": "^1.14", "mongodb/mongodb": "1.8.0", "phpunit/phpunit": "^9.4", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.11.0", + "utopia-php/cli": "^0.14.0", "vimeo/psalm": "4.0.1" }, "type": "library", @@ -385,22 +384,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.29.0" + "source": "https://github.com/utopia-php/database/tree/0.30.1" }, - "time": "2023-01-15T23:51:06+00:00" + "time": "2023-02-14T06:25:03+00:00" }, { "name": "utopia-php/framework", - "version": "0.26.0", + "version": "0.27.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e" + "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/b8d0447f5c98291d7759db05460ecced29a0f9ee", + "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee", "shasum": "" }, "require": { @@ -429,9 +428,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.26.0" + "source": "https://github.com/utopia-php/framework/tree/0.27.0" }, - "time": "2023-01-13T08:14:43+00:00" + "time": "2023-01-29T05:36:17+00:00" }, { "name": "utopia-php/mongo", @@ -1581,12 +1580,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6" + "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9334ff3ded6489b78dc95161a1fbab5bed64b7c6", - "reference": "9334ff3ded6489b78dc95161a1fbab5bed64b7c6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cf2107d2c96e9653f6267a15e29e38fb4773e0a5", + "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5", "shasum": "" }, "require": { @@ -1632,7 +1631,7 @@ "type": "tidelift" } ], - "time": "2023-01-27T20:41:39+00:00" + "time": "2023-02-12T08:37:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1954,16 +1953,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.28", + "version": "9.6.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", "shasum": "" }, "require": { @@ -2005,7 +2004,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -2036,7 +2035,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" }, "funding": [ { @@ -2052,7 +2051,7 @@ "type": "tidelift" } ], - "time": "2023-01-14T12:32:24+00:00" + "time": "2023-02-04T13:37:15+00:00" }, { "name": "psr/container", @@ -2523,16 +2522,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -2574,7 +2573,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -2582,7 +2581,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -2896,16 +2895,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -2944,10 +2943,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -2955,7 +2954,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -3014,16 +3013,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -3058,7 +3057,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -3066,7 +3065,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -4189,5 +4188,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } From 9627f6249a967e36d1413f9770a187e2ba441f2e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 17 Feb 2023 16:11:52 +1300 Subject: [PATCH 169/470] Update workflows --- .github/workflows/codeql-analysis.yml | 20 +++++++-------- .github/workflows/linter.yml | 2 +- .github/workflows/tests.yml | 24 ++++++++++++++++++ .travis.yml | 35 --------------------------- Dockerfile-php8 => Dockerfile | 0 docker-compose.yml | 4 +-- tests/Abuse/AbuseTest.php | 2 +- 7 files changed, 38 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml rename Dockerfile-php8 => Dockerfile (100%) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 734db04cc84..e5100dba6db 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,20 +1,20 @@ name: "CodeQL" -on: [pull_request] +on: [ pull_request ] jobs: lint: name: CodeQL runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 - - run: git checkout HEAD^2 + - run: git checkout HEAD^2 - - name: Run CodeQL - run: | - docker run --rm -v $PWD:/app composer sh -c \ - "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file + - name: Run CodeQL + run: | + docker run --rm -v $PWD:/app composer sh -c \ + "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index bca79de9faa..15853dbe01b 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,6 +1,6 @@ name: "Linter" -on: [pull_request] +on: [ pull_request ] jobs: lint: name: Linter diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000000..31158bece78 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,24 @@ +name: "Tests" + +on: [ pull_request ] +jobs: + lint: + name: Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - run: git checkout HEAD^2 + + - name: Build + run: | + docker compose build + docker compose up -d + sleep 10 + + - name: Run Tests + run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 08f1548cc71..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -dist: focal - -arch: - - amd64 - -os: linux - -language: shell - -notifications: - email: - - team@appwrite.io - -before_script: docker run --rm --interactive --tty --volume "$(pwd)":/app composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist - -before_install: -- curl -fsSL https://get.docker.com | sh -- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json -- mkdir -p $HOME/.docker -- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json -- sudo service docker start -- > - if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then - echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin - fi -- docker --version - -install: -- docker-compose up -d -- sleep 10 - -script: -- docker ps -- docker-compose exec php8 vendor/bin/phpunit --configuration phpunit.xml tests -- docker-compose exec php8 vendor/bin/psalm --show-info=true diff --git a/Dockerfile-php8 b/Dockerfile similarity index 100% rename from Dockerfile-php8 rename to Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index 034d2b704bd..907af1644bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,10 +10,10 @@ services: ports: - "9307:3306" - php8: + tests: build: context: . - dockerfile: ./Dockerfile-php8 + dockerfile: ./Dockerfile networks: - abuse depends_on: diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 48991df758e..7e4fd2a9c13 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -36,7 +36,7 @@ public function setUp(): void $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); if (! $db->exists('utopiaTests')) { - $db->create(); + $db->create('utopiaTests'); $adapter->setup(); } From a8d18e70684a689ca82131848a94497e0057cb62 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 17 Feb 2023 16:18:32 +1300 Subject: [PATCH 170/470] Fix checks --- composer.json | 3 +- composer.lock | 3491 +++++++++---------------------------- tests/Abuse/AbuseTest.php | 2 +- 3 files changed, 834 insertions(+), 2662 deletions(-) diff --git a/composer.json b/composer.json index f03be2dfd97..ef6f8ba00a8 100755 --- a/composer.json +++ b/composer.json @@ -21,8 +21,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.4", - "vimeo/psalm": "4.0.1", - "phpstan/phpstan": "1.9.x-dev", + "phpstan/phpstan": "^1.9", "laravel/pint": "1.2.*" } } diff --git a/composer.lock b/composer.lock index df984ce96f8..a04dba6a1d3 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": "34919b745bc4898bd9748cb49426c534", + "content-hash": "9a3c68b10b931d307c525e902f68688d", "packages": [ { "name": "composer/package-versions-deprecated", @@ -495,44 +495,36 @@ ], "packages-dev": [ { - "name": "amphp/amp", - "version": "v2.6.2", + "name": "doctrine/instantiator", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^8.1" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], "psr-4": { - "Amp\\": "lib" + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -541,86 +533,76 @@ ], "authors": [ { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" } ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" + "constructor", + "instantiate" ], "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { - "url": "https://github.com/amphp", - "type": "github" + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { - "name": "amphp/byte-stream", - "version": "v1.8.1", + "name": "laravel/pint", + "version": "v1.2.1", "source": { "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "url": "https://github.com/laravel/pint.git", + "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1", + "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1", "shasum": "" }, "require": { - "amphp/amp": "^2", - "php": ">=7.1" + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.0" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } + "friendsofphp/php-cs-fixer": "^3.11.0", + "illuminate/view": "^9.32.0", + "laravel-zero/framework": "^9.2.0", + "mockery/mockery": "^1.5.1", + "nunomaduro/larastan": "^2.2.0", + "nunomaduro/termwind": "^1.14.0", + "pestphp/pest": "^1.22.1" }, + "bin": [ + "builds/pint" + ], + "type": "project", "autoload": { - "files": [ - "lib/functions.php" - ], "psr-4": { - "Amp\\ByteStream\\": "lib" + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" } }, "notification-url": "https://packagist.org/downloads/", @@ -629,1801 +611,218 @@ ], "authors": [ { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" + "format", + "formatter", + "lint", + "linter", + "php" ], "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2022-11-29T16:25:20+00:00" }, { - "name": "composer/semver", - "version": "3.3.2", + "name": "myclabs/deep-copy", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "Composer\\Semver\\": "src" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { - "name": "composer/xdebug-handler", - "version": "1.4.6", + "name": "nikic/php-parser", + "version": "v4.15.3", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "f27e06cd9675801df441b3656569b328e04aa37c" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c", - "reference": "f27e06cd9675801df441b3656569b328e04aa37c", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0" + "ext-tokenizer": "*", + "php": ">=7.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, "autoload": { "psr-4": { - "Composer\\XdebugHandler\\": "src" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Nikita Popov" } ], - "description": "Restarts a process without Xdebug.", + "description": "A PHP parser written in PHP", "keywords": [ - "Xdebug", - "performance" + "parser", + "php" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/1.4.6" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-03-25T17:01:18+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", + "name": "phar-io/manifest", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } ], - "description": "implementation of xdg base directory specification for php", + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2019-12-04T15:06:13+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { - "name": "doctrine/instantiator", - "version": "2.0.0", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:23:10+00:00" - }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" - }, - "time": "2022-03-02T22:36:06+00:00" - }, - { - "name": "laravel/pint", - "version": "v1.2.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/pint.git", - "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1", - "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "php": "^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.11.0", - "illuminate/view": "^9.32.0", - "laravel-zero/framework": "^9.2.0", - "mockery/mockery": "^1.5.1", - "nunomaduro/larastan": "^2.2.0", - "nunomaduro/termwind": "^1.14.0", - "pestphp/pest": "^1.22.1" - }, - "bin": [ - "builds/pint" - ], - "type": "project", - "autoload": { - "psr-4": { - "App\\": "app/", - "Database\\Seeders\\": "database/seeders/", - "Database\\Factories\\": "database/factories/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "An opinionated code formatter for PHP.", - "homepage": "https://laravel.com", - "keywords": [ - "format", - "formatter", - "lint", - "linter", - "php" - ], - "support": { - "issues": "https://github.com/laravel/pint/issues", - "source": "https://github.com/laravel/pint" - }, - "time": "2022-11-29T16:25:20+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2022-03-03T13:19:32+00:00" - }, - { - "name": "netresearch/jsonmapper", - "version": "v3.1.1", - "source": { - "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "ba09f0e456d4f00cef84e012da5715625594407c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/ba09f0e456d4f00cef84e012da5715625594407c", - "reference": "ba09f0e456d4f00cef84e012da5715625594407c", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v3.1.1" - }, - "time": "2020-11-02T19:19:54+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.15.3", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" - }, - "time": "2023-01-16T22:05:37+00:00" - }, - { - "name": "openlss/lib-array2xml", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/nullivex/lib-array2xml.git", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "LSS": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Bryan Tong", - "email": "bryan@nullivex.com", - "homepage": "https://www.nullivex.com" - }, - { - "name": "Tony Butler", - "email": "spudz76@gmail.com", - "homepage": "https://www.nullivex.com" - } - ], - "description": "Array2XML conversion library credit to lalit.org", - "homepage": "https://www.nullivex.com", - "keywords": [ - "array", - "array conversion", - "xml", - "xml conversion" - ], - "support": { - "issues": "https://github.com/nullivex/lib-array2xml/issues", - "source": "https://github.com/nullivex/lib-array2xml/tree/master" - }, - "time": "2019-03-29T20:06:56+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" - }, - "time": "2022-10-14T12:47:21+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.9.x-dev", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cf2107d2c96e9653f6267a15e29e38fb4773e0a5", - "reference": "cf2107d2c96e9653f6267a15e29e38fb4773e0a5", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.x" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2023-02-12T08:37:39+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.24", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-01-26T08:26:55+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.6.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.6-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2023-02-04T13:37:15+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:41:17+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { "classmap": [ "src/" @@ -2434,181 +833,128 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de", - "role": "lead" + "role": "Developer" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "sebastian/diff", - "version": "4.0.4", + "name": "phpstan/phpstan", + "version": "1.9.17", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", + "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": "^7.2|^8.0" }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } + "MIT" ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "dev", + "static analysis" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.9.17" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/ondrejmirtes", "type": "github" - } - ], - "time": "2020-10-26T13:10:38+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, - "funding": [ + }, { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/phpstan", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2023-02-08T12:25:00+00:00" }, { - "name": "sebastian/exporter", - "version": "4.0.5", + "name": "phpunit/php-code-coverage", + "version": "9.2.24", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.14", "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "ext-mbstring": "*", "phpunit/phpunit": "^9.3" }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -2623,34 +969,20 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "export", - "exporter" + "coverage", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" }, "funding": [ { @@ -2658,38 +990,32 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2023-01-26T08:26:55+00:00" }, { - "name": "sebastian/global-state", - "version": "5.0.5", + "name": "phpunit/php-file-iterator", + "version": "3.0.6", "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=7.3" }, "require-dev": { - "ext-dom": "*", "phpunit/phpunit": "^9.3" }, - "suggest": { - "ext-uopz": "*" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2704,17 +1030,19 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "global state" + "filesystem", + "iterator" ], "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -2722,33 +1050,36 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "1.0.3", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", "php": ">=7.3" }, "require-dev": { + "ext-pcntl": "*", "phpunit/phpunit": "^9.3" }, + "suggest": { + "ext-pcntl": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -2767,11 +1098,14 @@ "role": "lead" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" }, "funding": [ { @@ -2779,26 +1113,24 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "4.0.4", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=7.3" }, "require-dev": { "phpunit/phpunit": "^9.3" @@ -2806,7 +1138,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2821,14 +1153,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, "funding": [ { @@ -2836,20 +1172,20 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "sebastian/object-reflector", - "version": "2.0.4", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { @@ -2861,7 +1197,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2876,14 +1212,18 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, "funding": [ { @@ -2891,35 +1231,68 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { - "name": "sebastian/recursion-context", - "version": "4.0.5", + "name": "phpunit/phpunit", + "version": "9.6.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", "shasum": "" }, "require": { - "php": ">=7.3" + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -2931,55 +1304,61 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" }, "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2023-02-04T13:37:15+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.3", + "name": "sebastian/cli-parser", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -2994,14 +1373,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" }, "funding": [ { @@ -3009,32 +1389,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2020-09-28T06:08:49+00:00" }, { - "name": "sebastian/type", - "version": "3.2.1", + "name": "sebastian/code-unit", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -3053,11 +1433,11 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" }, "funding": [ { @@ -3065,29 +1445,32 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { "php": ">=7.3" }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3102,15 +1485,14 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -3118,775 +1500,604 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { - "name": "symfony/console", - "version": "v5.4.19", + "name": "sebastian/comparator", + "version": "4.0.8", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", - "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ - "cli", - "command line", - "console", - "terminal" + "comparator", + "compare", + "equality" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.19" + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.2.0", + "name": "sebastian/complexity", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "shasum": "" }, "require": { - "php": ">=8.1" + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "2.0-dev" } }, "autoload": { - "files": [ - "function.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2020-10-26T15:52:27+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "name": "sebastian/diff", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" + "php": ">=7.3" }, - "suggest": { - "ext-ctype": "For best performance" + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "4.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "name": "sebastian/environment", + "version": "5.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-intl": "For best performance" + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "5.1-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" + "Xdebug", + "environment", + "hhvm" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "name": "sebastian/exporter", + "version": "4.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, - "suggest": { - "ext-intl": "For best performance" + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "4.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "export", + "exporter" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "name": "sebastian/global-state", + "version": "5.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, - "provide": { - "ext-mbstring": "*" + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-uopz": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "5.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "global state" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "name": "sebastian/lines-of-code", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "shasum": "" }, "require": { - "php": ">=7.1" + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2020-11-28T06:42:11+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.2.0", + "name": "sebastian/object-enumerator", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^2.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, - "suggest": { - "symfony/service-implementation": "" + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "4.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { - "name": "symfony/string", - "version": "v6.2.5", + "name": "sebastian/object-reflector", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.0" + "php": ">=7.3" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "source": "https://github.com/symfony/string/tree/v6.2.5" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.1", + "name": "sebastian/recursion-context", + "version": "4.0.5", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -3898,288 +2109,250 @@ ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://github.com/sebastianbergmann", "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { - "name": "vimeo/psalm", - "version": "4.0.1", + "name": "sebastian/resource-operations", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/vimeo/psalm.git", - "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44" + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/b1e2e30026936ef8d5bf6a354d1c3959b6231f44", - "reference": "b1e2e30026936ef8d5bf6a354d1c3959b6231f44", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { - "amphp/amp": "^2.1", - "amphp/byte-stream": "^1.5", - "composer/package-versions-deprecated": "^1.8.0", - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.1", - "dnoegel/php-xdg-base-dir": "^0.1.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.0.3", - "felixfbecker/language-server-protocol": "^1.4", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0", - "nikic/php-parser": "^4.10.1", - "openlss/lib-array2xml": "^1.0", - "php": "^7.3|^8", - "sebastian/diff": "^3.0 || ^4.0", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0", - "webmozart/glob": "^4.1", - "webmozart/path-util": "^2.3" - }, - "provide": { - "psalm/psalm": "self.version" + "php": ">=7.3" }, "require-dev": { - "amphp/amp": "^2.4.2", - "bamarni/composer-bin-plugin": "^1.2", - "brianium/paratest": "^4.0.0", - "ext-curl": "*", - "phpdocumentor/reflection-docblock": "^5", - "phpmyadmin/sql-parser": "5.1.0", - "phpspec/prophecy": ">=1.9.0", - "phpunit/phpunit": "^9.0", - "psalm/plugin-phpunit": "^0.13", - "slevomat/coding-standard": "^5.0", - "squizlabs/php_codesniffer": "^3.5", - "symfony/process": "^4.3", - "weirdan/prophecy-shim": "^1.0 || ^2.0" - }, - "suggest": { - "ext-igbinary": "^2.0.5" + "phpunit/phpunit": "^9.0" }, - "bin": [ - "psalm", - "psalm-language-server", - "psalm-plugin", - "psalm-refactor", - "psalter" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev", - "dev-3.x": "3.x-dev", - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "files": [ - "src/functions.php", - "src/spl_object_id.php" - ], - "psr-4": { - "Psalm\\": "src/Psalm/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Matthew Brown" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "A static analysis tool for finding errors in PHP applications", - "keywords": [ - "code", - "inspection", - "php" - ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.0.1" + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" }, - "time": "2020-10-20T13:40:17+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" }, { - "name": "webmozart/assert", - "version": "1.9.1", + "name": "sebastian/type", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^9.5" }, "type": "library", - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, - "time": "2020-07-08T17:02:28+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" }, { - "name": "webmozart/glob", - "version": "4.6.0", + "name": "sebastian/version", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/webmozarts/glob.git", - "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/glob/zipball/3c17f7dec3d9d0e87b575026011f2e75a56ed655", - "reference": "3c17f7dec3d9d0e87b575026011f2e75a56ed655", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": "^7.3 || ^8.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "symfony/filesystem": "^5.3" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { - "psr-4": { - "Webmozart\\Glob\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A PHP implementation of Ant's glob.", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/webmozarts/glob/issues", - "source": "https://github.com/webmozarts/glob/tree/4.6.0" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, - "time": "2022-05-24T19:45:58+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { - "name": "webmozart/path-util", - "version": "2.3.0", + "name": "theseer/tokenizer", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/webmozart/path-util.git", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { - "php": ">=5.3.3", - "webmozart/assert": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, "autoload": { - "psr-4": { - "Webmozart\\PathUtil\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { - "issues": "https://github.com/webmozart/path-util/issues", - "source": "https://github.com/webmozart/path-util/tree/2.3.0" + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, - "abandoned": "symfony/filesystem", - "time": "2015-12-17T08:42:14+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "phpstan/phpstan": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -4188,5 +2361,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 7e4fd2a9c13..48991df758e 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -36,7 +36,7 @@ public function setUp(): void $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); if (! $db->exists('utopiaTests')) { - $db->create('utopiaTests'); + $db->create(); $adapter->setup(); } From 1cf8232662dfa1c3472c67f2a8754ae108ba517a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 17 Feb 2023 20:03:29 +1300 Subject: [PATCH 171/470] Store time with millisecond precision instead of second --- src/Abuse/Adapters/TimeLimit.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 403f6a8adb5..f7b64a12f9f 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -50,15 +50,13 @@ class TimeLimit implements Adapter /** * @param string $key - * @param int $seconds * @param int $limit * @param Database $db */ public function __construct(string $key, int $limit, int $seconds, Database $db) { $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? - $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); + $this->time = DateTime::now(); $this->limit = $limit; $this->db = $db; } From 7ebbf2ebe5dd264c42709d2f5483e835429e87ae Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 17 Feb 2023 20:04:48 +1300 Subject: [PATCH 172/470] Remove redundant parameter --- src/Abuse/Adapters/TimeLimit.php | 2 +- tests/Abuse/AbuseTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f7b64a12f9f..eb3ba307df6 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -53,7 +53,7 @@ class TimeLimit implements Adapter * @param int $limit * @param Database $db */ - public function __construct(string $key, int $limit, int $seconds, Database $db) + public function __construct(string $key, int $limit, Database $db) { $this->key = $key; $this->time = DateTime::now(); diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 21f94012ca9..8455a7adf57 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -38,7 +38,7 @@ public function setUp(): void $db->setDefaultDatabase('utopiaTests'); $db->setNamespace('namespace'); - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -47,7 +47,7 @@ public function setUp(): void $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); - $this->abuseRace = new Abuse(new TimeLimit('increase', 999, 60 * 5, $db)); + $this->abuseRace = new Abuse(new TimeLimit('increase', 999, $db)); } public function tearDown(): void From 51dfdc53125eba3c377322976104a5eca45651b9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Feb 2023 17:11:09 +1300 Subject: [PATCH 173/470] Remove requirement to pass limit when getting logs --- composer.json | 5 +++- composer.lock | 39 ++++++++++++++++++++------------ src/Abuse/Adapters/TimeLimit.php | 39 ++++++++++++++++++-------------- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index ef6f8ba00a8..09cd4a21d9c 100755 --- a/composer.json +++ b/composer.json @@ -8,6 +8,9 @@ "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, + "autoload-dev": { + "psr-4": {"Utopia\\Tests\\": "tests/Abuse"} + }, "scripts": { "check": "./vendor/bin/phpstan analyse --level max src tests", "lint": "./vendor/bin/pint --test", @@ -17,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.30.*" + "utopia-php/database": "dev-refactor-limits as 0.30.1" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index a04dba6a1d3..af55bc04364 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": "9a3c68b10b931d307c525e902f68688d", + "content-hash": "fcc25101e0693c84eb71b0288c101b42", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.30.1", + "version": "dev-refactor-limits", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65" + "reference": "d83a3e820c641ba0132c076172be9397df943a89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65", - "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d83a3e820c641ba0132c076172be9397df943a89", + "reference": "d83a3e820c641ba0132c076172be9397df943a89", "shasum": "" }, "require": { @@ -384,9 +384,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.30.1" + "source": "https://github.com/utopia-php/database/tree/refactor-limits" }, - "time": "2023-02-14T06:25:03+00:00" + "time": "2023-02-15T07:32:21+00:00" }, { "name": "utopia-php/framework", @@ -858,16 +858,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.17", + "version": "1.9.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" + "reference": "f2d5cf71be91172a57c649770b73c20ebcffb0bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f2d5cf71be91172a57c649770b73c20ebcffb0bf", + "reference": "f2d5cf71be91172a57c649770b73c20ebcffb0bf", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.17" + "source": "https://github.com/phpstan/phpstan/tree/1.9.18" }, "funding": [ { @@ -913,7 +913,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T12:25:00+00:00" + "time": "2023-02-17T15:01:27+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2350,9 +2350,18 @@ "time": "2021-07-28T10:34:58+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-refactor-limits", + "alias": "0.30.1", + "alias_normalized": "0.30.1.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 8fdadf686b9..660cde17660 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -187,7 +187,7 @@ protected function count(string $key, string $datetime): int return $this->count; } - /** @var array $result */ + /** @var array $result */ $result = Authorization::skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), @@ -195,11 +195,6 @@ protected function count(string $key, string $datetime): int ]); }); - $this->count = 0; - if (\count($result) === 1) { - $this->count = intval($result[0]->getAttribute('count', 0)); - } - $this->count = (int) $result; return $this->count; @@ -210,7 +205,7 @@ protected function count(string $key, string $datetime): int * @param string $datetime * @return void * - * @throws AuthorizationException|Structure|\Exception + * @throws AuthorizationException|Structure|\Exception|\Throwable */ protected function hit(string $key, string $datetime): void { @@ -246,20 +241,31 @@ protected function hit(string $key, string $datetime): void /** * Get abuse logs * - * Return logs with an offset and limit + * Return logs with an optional offset and limit * - * @param int $offset - * @param int $limit - * @return array + * @param int|null $offset + * @param int|null $limit + * @return array * * @throws \Exception */ - public function getLogs(int $offset, int $limit): array + public function getLogs(?int $offset = null, ?int $limit = null): array { - /** @phpstan-ignore-next-line */ - return Authorization::skip(function () use ($offset, $limit) { - return $this->db->find(TimeLimit::COLLECTION, [Query::limit($limit), Query::offset($offset), Query::orderDesc('')]); + /** @var array $results */ + $results = Authorization::skip(function () use ($offset, $limit) { + $queries = []; + + if (! \is_null($offset)) { + $queries[] = Query::offset($offset); + } + if (! \is_null($limit)) { + $queries[] = Query::limit($limit); + } + + return $this->db->find(TimeLimit::COLLECTION, $queries); }); + + return $results; } /** @@ -278,9 +284,8 @@ public function cleanup(string $datetime): bool Query::lessThan('time', $datetime), ]); - /** @var array $document */ foreach ($documents as $document) { - $this->db->deleteDocument(TimeLimit::COLLECTION, $document['$id']); + $this->db->deleteDocument(TimeLimit::COLLECTION, $document->getId()); } } while (! empty($documents)); }); From eab350b052c6bd688a72899a0ed4a5afd04aafb4 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 24 Feb 2023 12:27:15 -0800 Subject: [PATCH 174/470] Bump utopia-php/database to 0.31.* --- composer.json | 2 +- composer.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index ef6f8ba00a8..640ce8155db 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.30.*" + "utopia-php/database": "0.31.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index a04dba6a1d3..2322a824509 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": "9a3c68b10b931d307c525e902f68688d", + "content-hash": "fd2eb793182c550f053111494b4fb5e5", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.30.1", + "version": "0.31.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65" + "reference": "61f9f4743a317f1d78558a5f981adf74e7fdc931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65", - "reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65", + "url": "https://api.github.com/repos/utopia-php/database/zipball/61f9f4743a317f1d78558a5f981adf74e7fdc931", + "reference": "61f9f4743a317f1d78558a5f981adf74e7fdc931", "shasum": "" }, "require": { @@ -384,9 +384,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.30.1" + "source": "https://github.com/utopia-php/database/tree/0.31.0" }, - "time": "2023-02-14T06:25:03+00:00" + "time": "2023-02-23T09:49:44+00:00" }, { "name": "utopia-php/framework", @@ -858,16 +858,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.17", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" + "reference": "a2ffec7db373d8da4973d1d62add872db5cd22dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2ffec7db373d8da4973d1d62add872db5cd22dd", + "reference": "a2ffec7db373d8da4973d1d62add872db5cd22dd", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.17" + "source": "https://github.com/phpstan/phpstan/tree/1.10.2" }, "funding": [ { @@ -913,7 +913,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T12:25:00+00:00" + "time": "2023-02-23T14:36:46+00:00" }, { "name": "phpunit/php-code-coverage", From ddea81baa8ba85c0a874b12d58297af68855c75a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 28 Feb 2023 16:31:48 +1300 Subject: [PATCH 175/470] Add 25 default limit --- src/Abuse/Abuse.php | 2 +- src/Abuse/Adapters/ReCaptcha.php | 2 +- src/Abuse/Adapters/TimeLimit.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 8e66211f77b..a1cbdce825a 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -38,7 +38,7 @@ public function check(): bool * @param int $limit * @return array */ - public function getLogs(int $offset, int $limit): array + public function getLogs(?int $offset, ?int $limit = 25): array { return $this->adapter->getLogs($offset, $limit); } diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index ac765ff04ae..d8759be4068 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -113,7 +113,7 @@ public function cleanup(string $datetime): bool * * @throws Exception */ - public function getLogs(int $offset, int $limit): array + public function getLogs(?int $offset = null, ?int $limit = 25): array { throw new Exception('Method not supported'); } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 660cde17660..e8896d1b346 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -249,7 +249,7 @@ protected function hit(string $key, string $datetime): void * * @throws \Exception */ - public function getLogs(?int $offset = null, ?int $limit = null): array + public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ $results = Authorization::skip(function () use ($offset, $limit) { From 3aec5712305646e37faefb6e0f31b6da1ca5fa94 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 28 Feb 2023 16:33:13 +1300 Subject: [PATCH 176/470] Fix type hint --- src/Abuse/Abuse.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index a1cbdce825a..50f115a740f 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -34,8 +34,8 @@ public function check(): bool * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit + * @param int|null $offset + * @param int|null $limit * @return array */ public function getLogs(?int $offset, ?int $limit = 25): array From 8edace4ef8900c5db6c13fe67ae00fab163aae0b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 28 Feb 2023 17:06:43 +1300 Subject: [PATCH 177/470] Checkstyle fix --- src/Abuse/Adapter.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 027ad60c42e..5f3af5820d7 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -18,13 +18,11 @@ public function check(): bool; * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * @param int $offset - * @param int $limit + * @param int|null $offset + * @param int|null $limit * @return array */ - public function getLogs(int $offset, int $limit): array; + public function getLogs(?int $offset, ?int $limit): array; /** * Delete all logs older than $datetime From f4435fd9c0dcfefdcdc2dda65de9d6e1c49c03bf Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 2 Mar 2023 01:47:35 +0530 Subject: [PATCH 178/470] chore: update database dependency --- Dockerfile | 2 +- composer.json | 2 +- composer.lock | 64 +++++++++++++++++++++++++-------------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b818334051..8a250999571 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /src/ COPY composer.lock /src/ COPY composer.json /src/ -RUN composer update --ignore-platform-reqs --optimize-autoloader \ +RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist FROM php:8.0-cli-alpine as final diff --git a/composer.json b/composer.json index 640ce8155db..813eb646e62 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.31.*" + "utopia-php/database": "0.32.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 2322a824509..4d419925e48 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": "fd2eb793182c550f053111494b4fb5e5", + "content-hash": "95abdbb48bf3dc6ee5a15916148540e5", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.31.0", + "version": "0.32.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "61f9f4743a317f1d78558a5f981adf74e7fdc931" + "reference": "f252f9cb58d93a9e2ef99915d743151da6f4e218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/61f9f4743a317f1d78558a5f981adf74e7fdc931", - "reference": "61f9f4743a317f1d78558a5f981adf74e7fdc931", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f252f9cb58d93a9e2ef99915d743151da6f4e218", + "reference": "f252f9cb58d93a9e2ef99915d743151da6f4e218", "shasum": "" }, "require": { @@ -384,22 +384,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.31.0" + "source": "https://github.com/utopia-php/database/tree/0.32.0" }, - "time": "2023-02-23T09:49:44+00:00" + "time": "2023-03-01T20:11:40+00:00" }, { "name": "utopia-php/framework", - "version": "0.27.0", + "version": "0.28.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee" + "reference": "d4f36ae8f62803a37dae3548977b8079dd045374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/b8d0447f5c98291d7759db05460ecced29a0f9ee", - "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/d4f36ae8f62803a37dae3548977b8079dd045374", + "reference": "d4f36ae8f62803a37dae3548977b8079dd045374", "shasum": "" }, "require": { @@ -428,9 +428,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.27.0" + "source": "https://github.com/utopia-php/framework/tree/0.28.0" }, - "time": "2023-01-29T05:36:17+00:00" + "time": "2023-03-01T19:48:36+00:00" }, { "name": "utopia-php/mongo", @@ -858,16 +858,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.2", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "a2ffec7db373d8da4973d1d62add872db5cd22dd" + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2ffec7db373d8da4973d1d62add872db5cd22dd", - "reference": "a2ffec7db373d8da4973d1d62add872db5cd22dd", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.2" + "source": "https://github.com/phpstan/phpstan/tree/1.10.3" }, "funding": [ { @@ -913,27 +913,27 @@ "type": "tidelift" } ], - "time": "2023-02-23T14:36:46+00:00" + "time": "2023-02-25T14:47:13+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "version": "9.2.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -982,7 +982,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" }, "funding": [ { @@ -990,7 +990,7 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-02-25T05:32:00+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1235,16 +1235,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.3", + "version": "9.6.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" + "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", + "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", "shasum": "" }, "require": { @@ -1317,7 +1317,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" }, "funding": [ { @@ -1333,7 +1333,7 @@ "type": "tidelift" } ], - "time": "2023-02-04T13:37:15+00:00" + "time": "2023-02-27T13:06:37+00:00" }, { "name": "sebastian/cli-parser", From 41f03d967a7b736c88525ff2c76024f9c13036b8 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 2 Mar 2023 18:16:40 +0200 Subject: [PATCH 179/470] new test --- composer.lock | 14 +++++++------- phpunit.xml | 2 +- tests/Abuse/AbuseTest.php | 22 ++++++++++++++++++++-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/composer.lock b/composer.lock index 4d419925e48..a78a102f52f 100644 --- a/composer.lock +++ b/composer.lock @@ -390,16 +390,16 @@ }, { "name": "utopia-php/framework", - "version": "0.28.0", + "version": "0.28.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "d4f36ae8f62803a37dae3548977b8079dd045374" + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/d4f36ae8f62803a37dae3548977b8079dd045374", - "reference": "d4f36ae8f62803a37dae3548977b8079dd045374", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55", + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55", "shasum": "" }, "require": { @@ -428,9 +428,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.0" + "source": "https://github.com/utopia-php/framework/tree/0.28.1" }, - "time": "2023-03-01T19:48:36+00:00" + "time": "2023-03-02T08:16:01+00:00" }, { "name": "utopia-php/mongo", @@ -2361,5 +2361,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/phpunit.xml b/phpunit.xml index ec39a7edeaf..10d408caa08 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 8455a7adf57..4fffd9a8d7f 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -17,8 +17,9 @@ class AbuseTest extends TestCase { protected Abuse $abuse; - + protected Abuse $abuseIp; protected Abuse $abuseRace; + protected Database $db; /** * @throws Exception @@ -37,6 +38,7 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDefaultDatabase('utopiaTests'); $db->setNamespace('namespace'); + $this->db = $db; $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, $db); if (! $db->exists('utopiaTests')) { @@ -55,6 +57,22 @@ public function tearDown(): void unset($this->abuse); } + public function testImitateRequest(): void + { + $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $this->abuseIp = new Abuse($adapter); + $this->assertEquals($this->abuseIp->check(), false); + + usleep(1); + + $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $this->abuseIp = new Abuse($adapter); + $this->assertEquals($this->abuseIp->check(), true); + } + + public function testIsValid(): void { // Use vars to resolve adapter key @@ -67,9 +85,9 @@ public function testIsValid(): void public function testAbuse(): void { for ($i = 0; $i < 999; $i++) { + usleep(1); $this->assertEquals($this->abuseRace->check(), false); } - $this->assertEquals($this->abuseRace->check(), true); } From c67b02a0e946694db72a66eced140885f4e25320 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 2 Mar 2023 18:46:29 +0200 Subject: [PATCH 180/470] REMOVE OLD TESTS --- src/Abuse/Adapters/TimeLimit.php | 4 +--- tests/Abuse/AbuseTest.php | 17 ++--------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index eb3ba307df6..f92395b0b6f 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -193,12 +193,10 @@ protected function count(string $key, string $datetime): int }); $this->count = 0; - if (\count($result) === 1) { + if (\count($result) === 1) { // key + time is a unique index we get a single result $this->count = intval($result[0]->getAttribute('count', 0)); } - $this->count = (int) $result; - return $this->count; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 4fffd9a8d7f..92f26db8f6f 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -18,7 +18,6 @@ class AbuseTest extends TestCase { protected Abuse $abuse; protected Abuse $abuseIp; - protected Abuse $abuseRace; protected Database $db; /** @@ -48,8 +47,6 @@ public function setUp(): void $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); - - $this->abuseRace = new Abuse(new TimeLimit('increase', 999, $db)); } public function tearDown(): void @@ -57,7 +54,7 @@ public function tearDown(): void unset($this->abuse); } - public function testImitateRequest(): void + public function testImitate2Requests(): void { $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); $adapter->setParam('{{ip}}', '0.0.0.10'); @@ -69,10 +66,9 @@ public function testImitateRequest(): void $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); $adapter->setParam('{{ip}}', '0.0.0.10'); $this->abuseIp = new Abuse($adapter); - $this->assertEquals($this->abuseIp->check(), true); + $this->assertEquals($this->abuseIp->check(), true); // WAITING FOR CODE REVERT } - public function testIsValid(): void { // Use vars to resolve adapter key @@ -82,15 +78,6 @@ public function testIsValid(): void $this->assertEquals($this->abuse->check(), true); } - public function testAbuse(): void - { - for ($i = 0; $i < 999; $i++) { - usleep(1); - $this->assertEquals($this->abuseRace->check(), false); - } - $this->assertEquals($this->abuseRace->check(), true); - } - public function testCleanup(): void { // Check that there is only one log From ce63715e17a4bdc8faa664162e8b74da62755b30 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 2 Mar 2023 18:49:02 +0200 Subject: [PATCH 181/470] changes --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 10d408caa08..ec39a7edeaf 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > From a78276208a0c49cccd6850d89acdbea2ceafc31c Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 2 Mar 2023 19:36:29 +0200 Subject: [PATCH 182/470] question --- src/Abuse/Adapters/TimeLimit.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f92395b0b6f..4c2a76db017 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -8,7 +8,6 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Exception\Limit; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -197,6 +196,9 @@ protected function count(string $key, string $datetime): int $this->count = intval($result[0]->getAttribute('count', 0)); } + // todo: I do not understand this array to int casting? + $this->count = (int) $result; + return $this->count; } From fd5390860fca41295bbae97cda076667cdce6747 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 09:41:41 +0200 Subject: [PATCH 183/470] revert datetime --- src/Abuse/Adapters/TimeLimit.php | 7 +++++-- tests/Abuse/AbuseTest.php | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 4c2a76db017..f5035b36c90 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -8,6 +8,7 @@ use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Limit; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -49,13 +50,15 @@ class TimeLimit implements Adapter /** * @param string $key + * @param int $seconds * @param int $limit * @param Database $db */ - public function __construct(string $key, int $limit, Database $db) + public function __construct(string $key, int $limit, int $seconds, Database $db) { $this->key = $key; - $this->time = DateTime::now(); + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); $this->limit = $limit; $this->db = $db; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 92f26db8f6f..fb82689c5b6 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -39,7 +39,7 @@ public function setUp(): void $db->setNamespace('namespace'); $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -56,14 +56,14 @@ public function tearDown(): void public function testImitate2Requests(): void { - $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); + $adapter = new TimeLimit('ip-{{ip}}', 1,1, $this->db); $adapter->setParam('{{ip}}', '0.0.0.10'); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); - usleep(1); + sleep(2); - $adapter = new TimeLimit('ip-{{ip}}', 1, $this->db); + $adapter = new TimeLimit('ip-{{ip}}', 1,1, $this->db); $adapter->setParam('{{ip}}', '0.0.0.10'); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), true); // WAITING FOR CODE REVERT From 8899ee40c1113d1b418d8c6f76e934073709ebb5 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 10:00:48 +0200 Subject: [PATCH 184/470] fix tests after revert --- phpunit.xml | 2 +- tests/Abuse/AbuseTest.php | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index ec39a7edeaf..10d408caa08 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index fb82689c5b6..b83ed5c6d86 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -39,7 +39,7 @@ public function setUp(): void $db->setNamespace('namespace'); $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, (60 * 5), $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -56,17 +56,22 @@ public function tearDown(): void public function testImitate2Requests(): void { - $adapter = new TimeLimit('ip-{{ip}}', 1,1, $this->db); - $adapter->setParam('{{ip}}', '0.0.0.10'); + $key = '{{ip}}'; + $value = '0.0.0.10'; + + $adapter = new TimeLimit($key, 1,1, $this->db); + $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); + $this->assertEquals($this->abuseIp->check(), true); - sleep(2); + sleep(1); - $adapter = new TimeLimit('ip-{{ip}}', 1,1, $this->db); - $adapter->setParam('{{ip}}', '0.0.0.10'); + $adapter = new TimeLimit($key, 1,1, $this->db); + $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); - $this->assertEquals($this->abuseIp->check(), true); // WAITING FOR CODE REVERT + $this->assertEquals($this->abuseIp->check(), false); + $this->assertEquals($this->abuseIp->check(), true); } public function testIsValid(): void @@ -82,7 +87,7 @@ public function testCleanup(): void { // Check that there is only one log $logs = $this->abuse->getLogs(0, 10); - $this->assertEquals(2, \count($logs)); + $this->assertEquals(3, \count($logs)); sleep(5); // Delete the log From 8e98ee117497b925db9df171b17159b2e45413e2 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 10:07:59 +0200 Subject: [PATCH 185/470] revert old change --- src/Abuse/Adapters/TimeLimit.php | 8 ++++---- tests/Abuse/AbuseTest.php | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f5035b36c90..2cebaadaa0e 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -194,12 +194,12 @@ protected function count(string $key, string $datetime): int ]); }); - $this->count = 0; - if (\count($result) === 1) { // key + time is a unique index we get a single result - $this->count = intval($result[0]->getAttribute('count', 0)); + if (\count($result) === 1) { // Unique Index + $result = $result[0]->getAttribute('count', 0); + } else { + $result = 0; } - // todo: I do not understand this array to int casting? $this->count = (int) $result; return $this->count; diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index b83ed5c6d86..7124021f84a 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -70,8 +70,15 @@ public function testImitate2Requests(): void $adapter = new TimeLimit($key, 1,1, $this->db); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); + + var_dump($adapter->remaining()); + + $this->assertEquals($this->abuseIp->check(), false); + var_dump($adapter->remaining()); $this->assertEquals($this->abuseIp->check(), true); + + } public function testIsValid(): void From fb3c2d1e89afe529cee164d3efe35ddba7f4cfb3 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 10:14:05 +0200 Subject: [PATCH 186/470] revert old change --- src/Abuse/Adapters/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 2cebaadaa0e..5d40a4719d4 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -2,13 +2,13 @@ namespace Utopia\Abuse\Adapters; +use Throwable; use Utopia\Abuse\Adapter; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Exception\Limit; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -210,7 +210,7 @@ protected function count(string $key, string $datetime): int * @param string $datetime * @return void * - * @throws AuthorizationException|Structure|\Exception + * @throws AuthorizationException|Structure|\Exception|Throwable */ protected function hit(string $key, string $datetime): void { From 4645a8cb56fb2018940aa47a937e4211367d3762 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 10:17:33 +0200 Subject: [PATCH 187/470] count fix --- src/Abuse/Adapter.php | 6 ++---- src/Abuse/Adapters/TimeLimit.php | 7 ++----- tests/Abuse/AbuseTest.php | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 027ad60c42e..8e6dc95de68 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -18,10 +18,8 @@ public function check(): bool; * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit - * @param int $offset - * @param int $limit + * @param int $offset + * @param int $limit * @return array */ public function getLogs(int $offset, int $limit): array; diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 5d40a4719d4..e684b7ac5ab 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -194,14 +194,11 @@ protected function count(string $key, string $datetime): int ]); }); + $this->count = 0; if (\count($result) === 1) { // Unique Index - $result = $result[0]->getAttribute('count', 0); - } else { - $result = 0; + $this->count = intval($result[0]->getAttribute('count', 0)); } - $this->count = (int) $result; - return $this->count; } diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 7124021f84a..dc207272590 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -73,7 +73,6 @@ public function testImitate2Requests(): void var_dump($adapter->remaining()); - $this->assertEquals($this->abuseIp->check(), false); var_dump($adapter->remaining()); $this->assertEquals($this->abuseIp->check(), true); From c146f8886d9f52fb750e8ea4006b23fececea7f4 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 10:17:53 +0200 Subject: [PATCH 188/470] count fix --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e684b7ac5ab..b36bfef621d 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -195,6 +195,7 @@ protected function count(string $key, string $datetime): int }); $this->count = 0; + if (\count($result) === 1) { // Unique Index $this->count = intval($result[0]->getAttribute('count', 0)); } From 8adab56ce1fcd038b47af38677c8ddb9536b1259 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 12:51:05 +0200 Subject: [PATCH 189/470] remove var dump --- tests/Abuse/AbuseTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index dc207272590..d7cd07f2a73 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -71,13 +71,8 @@ public function testImitate2Requests(): void $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); - var_dump($adapter->remaining()); - $this->assertEquals($this->abuseIp->check(), false); - var_dump($adapter->remaining()); $this->assertEquals($this->abuseIp->check(), true); - - } public function testIsValid(): void From 8c461b0b6f8bbb94fcb52550211b544d12beaf20 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 14:28:24 +0200 Subject: [PATCH 190/470] add Throwable --- src/Abuse/Adapters/TimeLimit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b36bfef621d..fe30211c8cf 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -309,7 +309,7 @@ public function cleanup(string $datetime): bool * * @return bool * - * @throws \Exception + * @throws \Exception|Throwable */ public function check(): bool { From 304138d00c8f9a429d08e2b709d135e810080c63 Mon Sep 17 00:00:00 2001 From: fogelito Date: Sun, 5 Mar 2023 14:40:08 +0200 Subject: [PATCH 191/470] lint fix --- src/Abuse/Adapter.php | 4 ++-- tests/Abuse/AbuseTest.php | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 8e6dc95de68..09c3c1035b9 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -18,8 +18,8 @@ public function check(): bool; * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit + * @param int $offset + * @param int $limit * @return array */ public function getLogs(int $offset, int $limit): array; diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index d7cd07f2a73..dac103dcc9c 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -17,7 +17,9 @@ class AbuseTest extends TestCase { protected Abuse $abuse; + protected Abuse $abuseIp; + protected Database $db; /** @@ -59,7 +61,7 @@ public function testImitate2Requests(): void $key = '{{ip}}'; $value = '0.0.0.10'; - $adapter = new TimeLimit($key, 1,1, $this->db); + $adapter = new TimeLimit($key, 1, 1, $this->db); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); @@ -67,7 +69,7 @@ public function testImitate2Requests(): void sleep(1); - $adapter = new TimeLimit($key, 1,1, $this->db); + $adapter = new TimeLimit($key, 1, 1, $this->db); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); From 55987e48e2f072cab24694b768da27f8379bdf20 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 10 Mar 2023 14:15:39 +0530 Subject: [PATCH 192/470] chore: update database dependency --- Dockerfile | 1 - composer.json | 2 +- composer.lock | 108 +++++++++++++++++++++++++------------------------- psalm.xml | 15 ------- 4 files changed, 55 insertions(+), 71 deletions(-) delete mode 100644 psalm.xml diff --git a/Dockerfile b/Dockerfile index 8a250999571..2fda963e65d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,5 @@ COPY --from=step0 /src/vendor /code/vendor COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml -COPY ./psalm.xml /code/psalm.xml CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/composer.json b/composer.json index 813eb646e62..2871b3be7c5 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.32.*" + "utopia-php/database": "0.33.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 4d419925e48..a9cb46bb1c0 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": "95abdbb48bf3dc6ee5a15916148540e5", + "content-hash": "7198dc0f6aee735b7b429ceaee72a481", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,23 +336,23 @@ }, { "name": "utopia-php/database", - "version": "0.32.0", + "version": "0.33.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f252f9cb58d93a9e2ef99915d743151da6f4e218" + "reference": "7453256728053cddfb0c872ac8ca60157c4d8a11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f252f9cb58d93a9e2ef99915d743151da6f4e218", - "reference": "f252f9cb58d93a9e2ef99915d743151da6f4e218", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7453256728053cddfb0c872ac8ca60157c4d8a11", + "reference": "7453256728053cddfb0c872ac8ca60157c4d8a11", "shasum": "" }, "require": { "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*", - "utopia-php/mongo": "0.0.2" + "utopia-php/mongo": "0.1.*" }, "require-dev": { "ext-mongodb": "*", @@ -384,22 +384,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.32.0" + "source": "https://github.com/utopia-php/database/tree/0.33.0" }, - "time": "2023-03-01T20:11:40+00:00" + "time": "2023-03-08T08:46:35+00:00" }, { "name": "utopia-php/framework", - "version": "0.28.0", + "version": "0.28.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "d4f36ae8f62803a37dae3548977b8079dd045374" + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/d4f36ae8f62803a37dae3548977b8079dd045374", - "reference": "d4f36ae8f62803a37dae3548977b8079dd045374", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55", + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55", "shasum": "" }, "require": { @@ -428,22 +428,22 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.0" + "source": "https://github.com/utopia-php/framework/tree/0.28.1" }, - "time": "2023-03-01T19:48:36+00:00" + "time": "2023-03-02T08:16:01+00:00" }, { "name": "utopia-php/mongo", - "version": "0.0.2", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e" + "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", - "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", + "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", "shasum": "" }, "require": { @@ -488,9 +488,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.0.2" + "source": "https://github.com/utopia-php/mongo/tree/0.1.0" }, - "time": "2022-11-08T11:58:46+00:00" + "time": "2023-01-12T14:02:08+00:00" } ], "packages-dev": [ @@ -632,16 +632,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -679,7 +679,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -687,20 +687,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -741,9 +741,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "phar-io/manifest", @@ -858,16 +858,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.3", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", "shasum": "" }, "require": { @@ -897,7 +897,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.3" + "source": "https://github.com/phpstan/phpstan/tree/1.10.6" }, "funding": [ { @@ -913,20 +913,20 @@ "type": "tidelift" } ], - "time": "2023-02-25T14:47:13+00:00" + "time": "2023-03-09T16:55:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.25", + "version": "9.2.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", "shasum": "" }, "require": { @@ -948,8 +948,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -982,7 +982,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" }, "funding": [ { @@ -990,7 +990,7 @@ "type": "github" } ], - "time": "2023-02-25T05:32:00+00:00" + "time": "2023-03-06T12:58:08+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1235,16 +1235,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.4", + "version": "9.6.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", "shasum": "" }, "require": { @@ -1277,8 +1277,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -1317,7 +1317,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" }, "funding": [ { @@ -1333,7 +1333,7 @@ "type": "tidelift" } ], - "time": "2023-02-27T13:06:37+00:00" + "time": "2023-03-09T06:34:10+00:00" }, { "name": "sebastian/cli-parser", diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 7c0333df3b9..00000000000 --- a/psalm.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - From 40b485627895b511cf55510756cf7c003b490a63 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Mar 2023 14:38:46 +1300 Subject: [PATCH 193/470] Fix defaults --- src/Abuse/Abuse.php | 2 +- src/Abuse/Adapter.php | 2 +- src/Abuse/Adapters/ReCaptcha.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 50f115a740f..7386c8a4faf 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -38,7 +38,7 @@ public function check(): bool * @param int|null $limit * @return array */ - public function getLogs(?int $offset, ?int $limit = 25): array + public function getLogs(?int $offset = null, ?int $limit = 25): array { return $this->adapter->getLogs($offset, $limit); } diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 5f3af5820d7..0cf1d280e12 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -22,7 +22,7 @@ public function check(): bool; * @param int|null $limit * @return array */ - public function getLogs(?int $offset, ?int $limit): array; + public function getLogs(?int $offset = null, ?int $limit = 25): array; /** * Delete all logs older than $datetime diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index d8759be4068..6f8a1797c94 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -107,8 +107,8 @@ public function cleanup(string $datetime): bool * * Return logs with an offset and limit * - * @param int $offset - * @param int $limit + * @param int|null $offset + * @param int|null $limit * @return array * * @throws Exception From 6daafa413cb7f2fa9d2d39ada8fb9adb4fe4b8ee Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Mar 2023 14:48:20 +1300 Subject: [PATCH 194/470] Composer update --- composer.json | 2 +- composer.lock | 121 ++++++++++++++++++++++++++------------------------ 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/composer.json b/composer.json index 60c4be88d6a..d80f8929d6d 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "dev-refactor-limits as 0.31.0" + "utopia-php/database": "dev-refactor-limits as 0.33.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 8890d89b72d..984ccb5f996 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": "c67ab840b0ad09ea8aba9db8b86a4026", + "content-hash": "8fc25dfe29a8940b537b4d1e767ad4dd", "packages": [ { "name": "composer/package-versions-deprecated", @@ -340,29 +340,33 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0cc311fcc6453045c8abfd32d4251a23432dec21" + "reference": "cc556a2afbd075ce0cdb45c80c55b860729c459c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0cc311fcc6453045c8abfd32d4251a23432dec21", - "reference": "0cc311fcc6453045c8abfd32d4251a23432dec21", + "url": "https://api.github.com/repos/utopia-php/database/zipball/cc556a2afbd075ce0cdb45c80c55b860729c459c", + "reference": "cc556a2afbd075ce0cdb45c80c55b860729c459c", "shasum": "" }, "require": { + "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*", - "utopia-php/mongo": "0.0.2" + "utopia-php/mongo": "0.1.*" }, "require-dev": { "ext-mongodb": "*", "ext-redis": "*", "fakerphp/faker": "^1.14", + "laravel/pint": "1.4.*", "mongodb/mongodb": "1.8.0", + "pcov/clobber": "^2.0", + "phpstan/phpstan": "1.9.*", "phpunit/phpunit": "^9.4", + "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.14.0", - "vimeo/psalm": "4.0.1" + "utopia-php/cli": "^0.14.0" }, "type": "library", "autoload": { @@ -374,7 +378,7 @@ "license": [ "MIT" ], - "description": "A simple library to manage application persistency using multiple database adapters", + "description": "A simple library to manage application persistence using multiple database adapters", "keywords": [ "database", "framework", @@ -386,20 +390,20 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/refactor-limits" }, - "time": "2023-02-28T03:27:21+00:00" + "time": "2023-03-23T01:45:21+00:00" }, { "name": "utopia-php/framework", - "version": "0.27.0", + "version": "0.28.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee" + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/b8d0447f5c98291d7759db05460ecced29a0f9ee", - "reference": "b8d0447f5c98291d7759db05460ecced29a0f9ee", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55", + "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55", "shasum": "" }, "require": { @@ -428,22 +432,22 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.27.0" + "source": "https://github.com/utopia-php/framework/tree/0.28.1" }, - "time": "2023-01-29T05:36:17+00:00" + "time": "2023-03-02T08:16:01+00:00" }, { "name": "utopia-php/mongo", - "version": "0.0.2", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e" + "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", - "reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", + "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", "shasum": "" }, "require": { @@ -488,9 +492,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.0.2" + "source": "https://github.com/utopia-php/mongo/tree/0.1.0" }, - "time": "2022-11-08T11:58:46+00:00" + "time": "2023-01-12T14:02:08+00:00" } ], "packages-dev": [ @@ -632,16 +636,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -679,7 +683,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -687,20 +691,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -741,9 +745,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "phar-io/manifest", @@ -858,16 +862,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.3", + "version": "1.10.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" + "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b10ceb526d9607903c5b2673f1fc8775dbe48975", + "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975", "shasum": "" }, "require": { @@ -896,8 +900,11 @@ "static analysis" ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.3" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -913,20 +920,20 @@ "type": "tidelift" } ], - "time": "2023-02-25T14:47:13+00:00" + "time": "2023-03-16T15:24:20+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.25", + "version": "9.2.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", "shasum": "" }, "require": { @@ -948,8 +955,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -982,7 +989,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" }, "funding": [ { @@ -990,7 +997,7 @@ "type": "github" } ], - "time": "2023-02-25T05:32:00+00:00" + "time": "2023-03-06T12:58:08+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1235,16 +1242,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.4", + "version": "9.6.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", - "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", "shasum": "" }, "require": { @@ -1277,8 +1284,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -1317,7 +1324,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" }, "funding": [ { @@ -1333,7 +1340,7 @@ "type": "tidelift" } ], - "time": "2023-02-27T13:06:37+00:00" + "time": "2023-03-09T06:34:10+00:00" }, { "name": "sebastian/cli-parser", @@ -2354,8 +2361,8 @@ { "package": "utopia-php/database", "version": "dev-refactor-limits", - "alias": "0.31.0", - "alias_normalized": "0.31.0.0" + "alias": "0.33.0", + "alias_normalized": "0.33.0.0" } ], "minimum-stability": "stable", From 7a9c5024e6d35a5be2fbf0db2d7359fffa5c763d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Mar 2023 15:00:08 +1300 Subject: [PATCH 195/470] Add back missing order desc --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e8896d1b346..42e8c629201 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -254,6 +254,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array /** @var array $results */ $results = Authorization::skip(function () use ($offset, $limit) { $queries = []; + $queries[] = Query::orderDesc(''); if (! \is_null($offset)) { $queries[] = Query::offset($offset); From 3b023b09e3f093c08ebd4639ba3a5ca374b0f129 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Mar 2023 18:26:23 +1300 Subject: [PATCH 196/470] Update database --- composer.json | 2 +- composer.lock | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index d80f8929d6d..2d990c57053 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "dev-refactor-limits as 0.33.0" + "utopia-php/database": "0.34.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 984ccb5f996..9a4ea66c8aa 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": "8fc25dfe29a8940b537b4d1e767ad4dd", + "content-hash": "48c701e8b661e7d3188c4c0ad4e57722", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "dev-refactor-limits", + "version": "0.34.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "cc556a2afbd075ce0cdb45c80c55b860729c459c" + "reference": "c0b2d93cd5b867f9816d89aa7883dda7e8373ea5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/cc556a2afbd075ce0cdb45c80c55b860729c459c", - "reference": "cc556a2afbd075ce0cdb45c80c55b860729c459c", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c0b2d93cd5b867f9816d89aa7883dda7e8373ea5", + "reference": "c0b2d93cd5b867f9816d89aa7883dda7e8373ea5", "shasum": "" }, "require": { @@ -388,9 +388,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/refactor-limits" + "source": "https://github.com/utopia-php/database/tree/0.34.0" }, - "time": "2023-03-23T01:45:21+00:00" + "time": "2023-03-23T05:24:05+00:00" }, { "name": "utopia-php/framework", @@ -2357,18 +2357,9 @@ "time": "2021-07-28T10:34:58+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-refactor-limits", - "alias": "0.33.0", - "alias_normalized": "0.33.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From b5112881c5721787afec7b2919230706d543aa65 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Mar 2023 20:25:28 +1300 Subject: [PATCH 197/470] Update database --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 9a4ea66c8aa..32f936835e5 100644 --- a/composer.lock +++ b/composer.lock @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.34.0", + "version": "0.34.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c0b2d93cd5b867f9816d89aa7883dda7e8373ea5" + "reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c0b2d93cd5b867f9816d89aa7883dda7e8373ea5", - "reference": "c0b2d93cd5b867f9816d89aa7883dda7e8373ea5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b592ab1bab9d7412cf5b756fdcdf850c03b1470d", + "reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d", "shasum": "" }, "require": { @@ -388,9 +388,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.34.0" + "source": "https://github.com/utopia-php/database/tree/0.34.1" }, - "time": "2023-03-23T05:24:05+00:00" + "time": "2023-03-23T07:23:41+00:00" }, { "name": "utopia-php/framework", From 64c4c99d86746ad069067af74bcfb049260d35c2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 11 Apr 2023 16:08:30 +1200 Subject: [PATCH 198/470] Update database --- composer.json | 2 +- composer.lock | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 2d990c57053..d68358e89d2 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.34.*" + "utopia-php/database": "0.35.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 3b3fe1b73f4..6f3291fd621 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": "48c701e8b661e7d3188c4c0ad4e57722", + "content-hash": "ac817075e64f9abd8a9c44bb3cbc73a4", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.34.1", + "version": "0.35.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d" + "reference": "f162c142fd61753c4b413b15c3c4041f3cd00bb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b592ab1bab9d7412cf5b756fdcdf850c03b1470d", - "reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f162c142fd61753c4b413b15c3c4041f3cd00bb2", + "reference": "f162c142fd61753c4b413b15c3c4041f3cd00bb2", "shasum": "" }, "require": { @@ -353,7 +353,7 @@ "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*", - "utopia-php/mongo": "0.1.*" + "utopia-php/mongo": "0.2.*" }, "require-dev": { "ext-mongodb": "*", @@ -362,7 +362,7 @@ "laravel/pint": "1.4.*", "mongodb/mongodb": "1.8.0", "pcov/clobber": "^2.0", - "phpstan/phpstan": "1.9.*", + "phpstan/phpstan": "1.10.*", "phpunit/phpunit": "^9.4", "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", @@ -388,9 +388,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.34.1" + "source": "https://github.com/utopia-php/database/tree/0.35.0" }, - "time": "2023-03-23T07:23:41+00:00" + "time": "2023-04-11T04:02:22+00:00" }, { "name": "utopia-php/framework", @@ -438,16 +438,16 @@ }, { "name": "utopia-php/mongo", - "version": "0.1.0", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f" + "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", - "reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", + "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", "shasum": "" }, "require": { @@ -492,9 +492,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.1.0" + "source": "https://github.com/utopia-php/mongo/tree/0.2.0" }, - "time": "2023-01-12T14:02:08+00:00" + "time": "2023-03-22T10:44:29+00:00" } ], "packages-dev": [ @@ -2368,5 +2368,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From 31c2291e6b2f5eca92da4e9d838d4fef19e2db2b Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 26 Apr 2023 12:04:25 +0200 Subject: [PATCH 199/470] chore: upgrade database version --- composer.json | 4 +-- composer.lock | 57 ++++++++++++++++---------------- pint.json | 3 ++ src/Abuse/Adapters/TimeLimit.php | 2 +- 4 files changed, 35 insertions(+), 31 deletions(-) create mode 100644 pint.json diff --git a/composer.json b/composer.json index d68358e89d2..545fc6fd985 100755 --- a/composer.json +++ b/composer.json @@ -20,11 +20,11 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.35.*" + "utopia-php/database": "0.36.*" }, "require-dev": { "phpunit/phpunit": "^9.4", "phpstan/phpstan": "^1.9", - "laravel/pint": "1.2.*" + "laravel/pint": "1.5.*" } } diff --git a/composer.lock b/composer.lock index 6f3291fd621..92a00433f05 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": "ac817075e64f9abd8a9c44bb3cbc73a4", + "content-hash": "3a11f0f0a1d282605f5135380e1094ff", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.35.0", + "version": "0.36.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f162c142fd61753c4b413b15c3c4041f3cd00bb2" + "reference": "7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f162c142fd61753c4b413b15c3c4041f3cd00bb2", - "reference": "f162c142fd61753c4b413b15c3c4041f3cd00bb2", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1", + "reference": "7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1", "shasum": "" }, "require": { @@ -388,9 +388,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.35.0" + "source": "https://github.com/utopia-php/database/tree/0.36.0" }, - "time": "2023-04-11T04:02:22+00:00" + "time": "2023-04-26T09:56:47+00:00" }, { "name": "utopia-php/framework", @@ -570,16 +570,16 @@ }, { "name": "laravel/pint", - "version": "v1.2.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1" + "reference": "e0a8cef58b74662f27355be9cdea0e726bbac362" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e60e2112ee779ce60f253695b273d1646a17d6f1", - "reference": "e60e2112ee779ce60f253695b273d1646a17d6f1", + "url": "https://api.github.com/repos/laravel/pint/zipball/e0a8cef58b74662f27355be9cdea0e726bbac362", + "reference": "e0a8cef58b74662f27355be9cdea0e726bbac362", "shasum": "" }, "require": { @@ -590,13 +590,13 @@ "php": "^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.11.0", - "illuminate/view": "^9.32.0", + "friendsofphp/php-cs-fixer": "^3.14.4", + "illuminate/view": "^9.51.0", "laravel-zero/framework": "^9.2.0", "mockery/mockery": "^1.5.1", - "nunomaduro/larastan": "^2.2.0", - "nunomaduro/termwind": "^1.14.0", - "pestphp/pest": "^1.22.1" + "nunomaduro/larastan": "^2.4.0", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^1.22.4" }, "bin": [ "builds/pint" @@ -632,7 +632,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2022-11-29T16:25:20+00:00" + "time": "2023-02-14T16:31:02+00:00" }, { "name": "myclabs/deep-copy", @@ -862,16 +862,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.7", + "version": "1.10.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975" + "reference": "d232901b09e67538e5c86a724be841bea5768a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b10ceb526d9607903c5b2673f1fc8775dbe48975", - "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", + "reference": "d232901b09e67538e5c86a724be841bea5768a7c", "shasum": "" }, "require": { @@ -920,7 +920,7 @@ "type": "tidelift" } ], - "time": "2023-03-16T15:24:20+00:00" + "time": "2023-04-19T13:47:27+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1242,16 +1242,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.5", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", - "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -1324,7 +1324,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -1340,7 +1341,7 @@ "type": "tidelift" } ], - "time": "2023-03-09T06:34:10+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "sebastian/cli-parser", diff --git a/pint.json b/pint.json new file mode 100644 index 00000000000..c7819336c36 --- /dev/null +++ b/pint.json @@ -0,0 +1,3 @@ +{ + "preset": "psr12" +} \ No newline at end of file diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 641f6131f73..e280b840f7c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -16,7 +16,7 @@ class TimeLimit implements Adapter { - const COLLECTION = 'abuse'; + public const COLLECTION = 'abuse'; /** * @var Database From 5c2d81c954195fdc4a0de056359803db0783bbe3 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 27 Apr 2023 10:44:39 +0200 Subject: [PATCH 200/470] chore: upgrade composer lock --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 92a00433f05..3d7240f6a2c 100644 --- a/composer.lock +++ b/composer.lock @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.36.0", + "version": "0.36.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1" + "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1", - "reference": "7e745d2204d007d1dcfa9b9dfa74f601aa38b6d1", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f6ab65e59a199da5155c114564077b1ab8c4daef", + "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef", "shasum": "" }, "require": { @@ -388,9 +388,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.36.0" + "source": "https://github.com/utopia-php/database/tree/0.36.1" }, - "time": "2023-04-26T09:56:47+00:00" + "time": "2023-04-27T08:39:55+00:00" }, { "name": "utopia-php/framework", From 69432e4fc3fd5bbe729d53c391c63396b32e2144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Obrtl=C3=ADk?= Date: Thu, 25 May 2023 17:28:09 +0200 Subject: [PATCH 201/470] Doc: Wrong comparison According to the [Appwrite usage of Abuse](https://github.com/appwrite/appwrite/blob/181741d7a2082f5c9d3ca727384601f3cf75e5bf/app/controllers/shared/api.php#LL164C22-L164C22), exception should be thrown when check returns true --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b1e777d53c8..e17e4ce7b43 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ $abuse = new Abuse($adapter); // Use vars to resolve adapter key -if(!$abuse->check()) { +if($abuse->check()) { throw new Exception('Service was abused!'); // throw error and return X-Rate limit headers here } ``` @@ -83,7 +83,7 @@ use Utopia\Abuse\Adapters\ReCaptcha; $adapter = new ReCaptcha('secret-api-key', $_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']); $abuse = new Abuse($adapter); -if(!$abuse->check()) { +if($abuse->check()) { throw new Exception('Service was abused!'); // throw error and return X-Rate limit headers here } ``` From 56c3b3a7c3a07eb800d5026e8d8f6a3f81de481e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Jun 2023 12:33:09 +1200 Subject: [PATCH 202/470] Update database --- composer.json | 2 +- composer.lock | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 545fc6fd985..62f38e3a31b 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.36.*" + "utopia-php/database": "0.37.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 3d7240f6a2c..40e9c67ecb0 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": "3a11f0f0a1d282605f5135380e1094ff", + "content-hash": "1b1799ee4dd23c8a57f598585fefbd1b", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.36.1", + "version": "0.37.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef" + "reference": "698c5a44598bc263c58506d4fd526589ea392114" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/f6ab65e59a199da5155c114564077b1ab8c4daef", - "reference": "f6ab65e59a199da5155c114564077b1ab8c4daef", + "url": "https://api.github.com/repos/utopia-php/database/zipball/698c5a44598bc263c58506d4fd526589ea392114", + "reference": "698c5a44598bc263c58506d4fd526589ea392114", "shasum": "" }, "require": { @@ -356,8 +356,6 @@ "utopia-php/mongo": "0.2.*" }, "require-dev": { - "ext-mongodb": "*", - "ext-redis": "*", "fakerphp/faker": "^1.14", "laravel/pint": "1.4.*", "mongodb/mongodb": "1.8.0", @@ -388,9 +386,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.36.1" + "source": "https://github.com/utopia-php/database/tree/0.37.0" }, - "time": "2023-04-27T08:39:55+00:00" + "time": "2023-06-09T07:39:11+00:00" }, { "name": "utopia-php/framework", From b06249d7d99b30c670ce22822b27e93539b39beb Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 14 Jul 2023 08:38:51 -0700 Subject: [PATCH 203/470] Bump database version --- composer.json | 2 +- composer.lock | 75 ++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 62f38e3a31b..2c76650e459 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.37.*" + "utopia-php/database": "0.38.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 40e9c67ecb0..26779648554 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": "1b1799ee4dd23c8a57f598585fefbd1b", + "content-hash": "830e1b26b89f89ca4e58f5ddc71a61f2", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.37.0", + "version": "0.38.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "698c5a44598bc263c58506d4fd526589ea392114" + "reference": "59e4684cf87e03c12dab9240158c1dfc6888e534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/698c5a44598bc263c58506d4fd526589ea392114", - "reference": "698c5a44598bc263c58506d4fd526589ea392114", + "url": "https://api.github.com/repos/utopia-php/database/zipball/59e4684cf87e03c12dab9240158c1dfc6888e534", + "reference": "59e4684cf87e03c12dab9240158c1dfc6888e534", "shasum": "" }, "require": { @@ -386,29 +386,30 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.37.0" + "source": "https://github.com/utopia-php/database/tree/0.38.0" }, - "time": "2023-06-09T07:39:11+00:00" + "time": "2023-07-14T07:49:38+00:00" }, { "name": "utopia-php/framework", - "version": "0.28.1", + "version": "0.28.4", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55" + "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55", - "reference": "7f22c556fc5991e54e5811a68fb39809b21bda55", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/98c5469efe195aeecc63745dbf8e2f357f8cedac", + "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.0" }, "require-dev": { "laravel/pint": "^1.2", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.5.25", "vimeo/psalm": "4.27.0" }, @@ -430,9 +431,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.1" + "source": "https://github.com/utopia-php/framework/tree/0.28.4" }, - "time": "2023-03-02T08:16:01+00:00" + "time": "2023-06-03T14:09:22+00:00" }, { "name": "utopia-php/mongo", @@ -693,16 +694,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.16.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", "shasum": "" }, "require": { @@ -743,9 +744,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-06-25T14:52:30+00:00" }, { "name": "phar-io/manifest", @@ -860,16 +861,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.14", + "version": "1.10.25", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c" + "reference": "578f4e70d117f9a90699324c555922800ac38d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", - "reference": "d232901b09e67538e5c86a724be841bea5768a7c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c", + "reference": "578f4e70d117f9a90699324c555922800ac38d8c", "shasum": "" }, "require": { @@ -918,7 +919,7 @@ "type": "tidelift" } ], - "time": "2023-04-19T13:47:27+00:00" + "time": "2023-07-06T12:11:37+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1240,16 +1241,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.7", + "version": "9.6.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", "shasum": "" }, "require": { @@ -1323,7 +1324,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" }, "funding": [ { @@ -1339,7 +1340,7 @@ "type": "tidelift" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2023-07-10T04:04:23+00:00" }, { "name": "sebastian/cli-parser", @@ -1641,16 +1642,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -1695,7 +1696,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1703,7 +1704,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", From d141307105a0e748bf099475251752aca4bf9ed8 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 14 Jul 2023 08:54:59 -0700 Subject: [PATCH 204/470] Fix codeql issues --- src/Abuse/Adapters/TimeLimit.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index e280b840f7c..b33c1e4fc17 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -197,7 +197,10 @@ protected function count(string $key, string $datetime): int $this->count = 0; if (\count($result) === 1) { // Unique Index - $this->count = intval($result[0]->getAttribute('count', 0)); + $count = $result[0]->getAttribute('count', 0); + if (\is_numeric($count)) { + $this->count = intval($count); + } } return $this->count; @@ -235,14 +238,16 @@ protected function hit(string $key, string $datetime): void $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } catch (Duplicate $e) { // Duplicate in case of race condition - /** @var Document $data */ $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); - if ($data != false) { - $this->count = intval($data->getAttribute('count')); + if ($data !== false && $data instanceof Document) { + $count = $data->getAttribute('count', 0); + if (\is_numeric($count)) { + $this->count = intval($count); + } $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } else { throw new \Exception('Document Not Found'); From c0553b8cea375f32115137088dd8e756b6ec0134 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 27 Jul 2023 03:02:23 +0530 Subject: [PATCH 205/470] bump update database --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2c76650e459..1604a9e73b8 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.38.*" + "utopia-php/database": "0.39.*" }, "require-dev": { "phpunit/phpunit": "^9.4", From dcab1cb41306c2ad8514f10c5d71047b2a8d995a Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 27 Jul 2023 03:08:33 +0530 Subject: [PATCH 206/470] updates composer lock file --- composer.lock | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 26779648554..a588f860a23 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": "830e1b26b89f89ca4e58f5ddc71a61f2", + "content-hash": "b2ca804f0a7e8b611a699c139b32671c", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.38.0", + "version": "0.39.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "59e4684cf87e03c12dab9240158c1dfc6888e534" + "reference": "a9f706034a12c7c87e6fd38eca69587fd3def0e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/59e4684cf87e03c12dab9240158c1dfc6888e534", - "reference": "59e4684cf87e03c12dab9240158c1dfc6888e534", + "url": "https://api.github.com/repos/utopia-php/database/zipball/a9f706034a12c7c87e6fd38eca69587fd3def0e0", + "reference": "a9f706034a12c7c87e6fd38eca69587fd3def0e0", "shasum": "" }, "require": { @@ -386,9 +386,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.38.0" + "source": "https://github.com/utopia-php/database/tree/0.39.0" }, - "time": "2023-07-14T07:49:38+00:00" + "time": "2023-07-26T18:03:25+00:00" }, { "name": "utopia-php/framework", @@ -861,16 +861,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.25", + "version": "1.10.26", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "578f4e70d117f9a90699324c555922800ac38d8c" + "reference": "5d660cbb7e1b89253a47147ae44044f49832351f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c", - "reference": "578f4e70d117f9a90699324c555922800ac38d8c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f", + "reference": "5d660cbb7e1b89253a47147ae44044f49832351f", "shasum": "" }, "require": { @@ -919,20 +919,20 @@ "type": "tidelift" } ], - "time": "2023-07-06T12:11:37+00:00" + "time": "2023-07-19T12:44:37+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", + "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", "shasum": "" }, "require": { @@ -988,7 +988,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" }, "funding": [ { @@ -996,7 +997,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-07-26T13:44:30+00:00" }, { "name": "phpunit/php-file-iterator", From 979d38934c291f720e31337a7400a96e8a566ca9 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Thu, 3 Aug 2023 13:48:59 +0530 Subject: [PATCH 207/470] bump db upgrade version --- composer.json | 2 +- composer.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 1604a9e73b8..c600b713d7b 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.39.*" + "utopia-php/database": "0.40.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index a588f860a23..53f63b8895d 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": "b2ca804f0a7e8b611a699c139b32671c", + "content-hash": "06a3c19acf31fbdaa0d92444a311c96b", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.39.0", + "version": "0.40.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "a9f706034a12c7c87e6fd38eca69587fd3def0e0" + "reference": "ec586ba1c5fc83856df4feaa05464da72b72ee0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/a9f706034a12c7c87e6fd38eca69587fd3def0e0", - "reference": "a9f706034a12c7c87e6fd38eca69587fd3def0e0", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ec586ba1c5fc83856df4feaa05464da72b72ee0d", + "reference": "ec586ba1c5fc83856df4feaa05464da72b72ee0d", "shasum": "" }, "require": { @@ -386,22 +386,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.39.0" + "source": "https://github.com/utopia-php/database/tree/0.40.0" }, - "time": "2023-07-26T18:03:25+00:00" + "time": "2023-08-03T08:01:37+00:00" }, { "name": "utopia-php/framework", - "version": "0.28.4", + "version": "0.29", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac" + "reference": "1c8ed95fb73531b0508ad69f85dafc4c78a64414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/98c5469efe195aeecc63745dbf8e2f357f8cedac", - "reference": "98c5469efe195aeecc63745dbf8e2f357f8cedac", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/1c8ed95fb73531b0508ad69f85dafc4c78a64414", + "reference": "1c8ed95fb73531b0508ad69f85dafc4c78a64414", "shasum": "" }, "require": { @@ -409,9 +409,9 @@ }, "require-dev": { "laravel/pint": "^1.2", - "phpstan/phpstan": "1.9.x-dev", - "phpunit/phpunit": "^9.5.25", - "vimeo/psalm": "4.27.0" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25" }, "type": "library", "autoload": { @@ -431,9 +431,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.28.4" + "source": "https://github.com/utopia-php/framework/tree/0.29" }, - "time": "2023-06-03T14:09:22+00:00" + "time": "2023-07-30T07:56:23+00:00" }, { "name": "utopia-php/mongo", @@ -1849,16 +1849,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -1901,7 +1901,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -1909,7 +1909,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", From 3bca9cbeeae4e413b64392a79dc936981e32a5e0 Mon Sep 17 00:00:00 2001 From: prateek banga Date: Wed, 9 Aug 2023 22:51:04 +0530 Subject: [PATCH 208/470] bump db version --- composer.json | 2 +- composer.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index c600b713d7b..c0e63d33499 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.40.*" + "utopia-php/database": "0.41.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 53f63b8895d..eb097eb100f 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": "06a3c19acf31fbdaa0d92444a311c96b", + "content-hash": "c1f3e6f58a7267e72e763bcb1f8e2ae4", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.40.0", + "version": "0.41.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ec586ba1c5fc83856df4feaa05464da72b72ee0d" + "reference": "98dc6a94e636fe8f955c20fa4833db8986b5c27a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ec586ba1c5fc83856df4feaa05464da72b72ee0d", - "reference": "ec586ba1c5fc83856df4feaa05464da72b72ee0d", + "url": "https://api.github.com/repos/utopia-php/database/zipball/98dc6a94e636fe8f955c20fa4833db8986b5c27a", + "reference": "98dc6a94e636fe8f955c20fa4833db8986b5c27a", "shasum": "" }, "require": { @@ -386,22 +386,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.40.0" + "source": "https://github.com/utopia-php/database/tree/0.41.0" }, - "time": "2023-08-03T08:01:37+00:00" + "time": "2023-08-09T02:52:31+00:00" }, { "name": "utopia-php/framework", - "version": "0.29", + "version": "0.30.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "1c8ed95fb73531b0508ad69f85dafc4c78a64414" + "reference": "0969d429997996ceade53e477fce31221b415651" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/1c8ed95fb73531b0508ad69f85dafc4c78a64414", - "reference": "1c8ed95fb73531b0508ad69f85dafc4c78a64414", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/0969d429997996ceade53e477fce31221b415651", + "reference": "0969d429997996ceade53e477fce31221b415651", "shasum": "" }, "require": { @@ -431,9 +431,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.29" + "source": "https://github.com/utopia-php/framework/tree/0.30.0" }, - "time": "2023-07-30T07:56:23+00:00" + "time": "2023-08-07T07:55:48+00:00" }, { "name": "utopia-php/mongo", @@ -861,16 +861,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.26", + "version": "1.10.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5d660cbb7e1b89253a47147ae44044f49832351f" + "reference": "e4545b55904ebef470423d3ddddb74fa7325497a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f", - "reference": "5d660cbb7e1b89253a47147ae44044f49832351f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e4545b55904ebef470423d3ddddb74fa7325497a", + "reference": "e4545b55904ebef470423d3ddddb74fa7325497a", "shasum": "" }, "require": { @@ -919,7 +919,7 @@ "type": "tidelift" } ], - "time": "2023-07-19T12:44:37+00:00" + "time": "2023-08-08T12:33:42+00:00" }, { "name": "phpunit/php-code-coverage", From af09e89ab1372830a6749c1d5c19b90f83b33f2c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 10 Aug 2023 19:25:59 -0400 Subject: [PATCH 209/470] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c0e63d33499..d27362bc290 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.41.*" + "utopia-php/database": "0.42.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index eb097eb100f..36e83e04342 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": "c1f3e6f58a7267e72e763bcb1f8e2ae4", + "content-hash": "6bc91675c4e24dc917ba60925cd8dc8d", "packages": [ { "name": "composer/package-versions-deprecated", @@ -336,16 +336,16 @@ }, { "name": "utopia-php/database", - "version": "0.41.0", + "version": "0.42.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "98dc6a94e636fe8f955c20fa4833db8986b5c27a" + "reference": "5631f151ce2b454517f11c1914e50a1fa70c7bd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/98dc6a94e636fe8f955c20fa4833db8986b5c27a", - "reference": "98dc6a94e636fe8f955c20fa4833db8986b5c27a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/5631f151ce2b454517f11c1914e50a1fa70c7bd1", + "reference": "5631f151ce2b454517f11c1914e50a1fa70c7bd1", "shasum": "" }, "require": { @@ -386,9 +386,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.41.0" + "source": "https://github.com/utopia-php/database/tree/0.42.0" }, - "time": "2023-08-09T02:52:31+00:00" + "time": "2023-08-10T23:18:38+00:00" }, { "name": "utopia-php/framework", From 9eb3ff2928aece9782ff5c7f08ac59bbde6731c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Aug 2023 10:42:58 +0000 Subject: [PATCH 210/470] Upgrade DB version --- composer.json | 2 +- composer.lock | 63 ++++++++++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/composer.json b/composer.json index d27362bc290..9cf6e322782 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.42.*" + "utopia-php/database": "0.43.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 36e83e04342..2ea78ab3017 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": "6bc91675c4e24dc917ba60925cd8dc8d", + "content-hash": "f8bfbfa488c383d57b21f50b36546c2d", "packages": [ { "name": "composer/package-versions-deprecated", @@ -204,16 +204,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -222,7 +222,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -267,7 +267,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -283,7 +283,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "utopia-php/cache", @@ -336,19 +336,20 @@ }, { "name": "utopia-php/database", - "version": "0.42.0", + "version": "0.43.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "5631f151ce2b454517f11c1914e50a1fa70c7bd1" + "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/5631f151ce2b454517f11c1914e50a1fa70c7bd1", - "reference": "5631f151ce2b454517f11c1914e50a1fa70c7bd1", + "url": "https://api.github.com/repos/utopia-php/database/zipball/fb96fc6c94d5efcd43913c34bece62daba76a5e9", + "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9", "shasum": "" }, "require": { + "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.8.*", @@ -386,9 +387,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.42.0" + "source": "https://github.com/utopia-php/database/tree/0.43.0" }, - "time": "2023-08-10T23:18:38+00:00" + "time": "2023-08-29T10:18:39+00:00" }, { "name": "utopia-php/framework", @@ -694,16 +695,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.16.0", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -744,9 +745,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-06-25T14:52:30+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "phar-io/manifest", @@ -861,16 +862,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.28", + "version": "1.10.32", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e4545b55904ebef470423d3ddddb74fa7325497a" + "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e4545b55904ebef470423d3ddddb74fa7325497a", - "reference": "e4545b55904ebef470423d3ddddb74fa7325497a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", + "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", "shasum": "" }, "require": { @@ -919,7 +920,7 @@ "type": "tidelift" } ], - "time": "2023-08-08T12:33:42+00:00" + "time": "2023-08-24T21:54:50+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1242,16 +1243,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.10", + "version": "9.6.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", "shasum": "" }, "require": { @@ -1325,7 +1326,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" }, "funding": [ { @@ -1341,7 +1342,7 @@ "type": "tidelift" } ], - "time": "2023-07-10T04:04:23+00:00" + "time": "2023-08-19T07:10:56+00:00" }, { "name": "sebastian/cli-parser", From 8efe079a3ab847a3e20ae102a27348e5d3313819 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 17 Oct 2023 16:43:45 +1300 Subject: [PATCH 211/470] Update database --- composer.json | 2 +- composer.lock | 165 +++++++++++++++----------------------------------- 2 files changed, 49 insertions(+), 118 deletions(-) diff --git a/composer.json b/composer.json index 9cf6e322782..f4fad25f1ce 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.43.*" + "utopia-php/database": "0.44.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 2ea78ab3017..661aa654e10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,101 +4,32 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8bfbfa488c383d57b21f50b36546c2d", + "content-hash": "b26596d08cac61eb5aa80b96d8f550c5", "packages": [ - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, { "name": "jean85/pretty-package-versions", - "version": "1.6.0", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303" + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.8.0", - "php": "^7.0|^8.0" + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0|^8.5|^9.2" + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" }, "type": "library", "extra": { @@ -121,7 +52,7 @@ "email": "alessandro.lai85@gmail.com" } ], - "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "description": "A library to get pretty versions strings of installed dependencies", "keywords": [ "composer", "package", @@ -130,40 +61,41 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" }, - "time": "2021-02-04T16:20:16+00:00" + "time": "2021-10-08T21:21:46+00:00" }, { "name": "mongodb/mongodb", - "version": "1.8.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", + "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", "shasum": "" }, "require": { "ext-hash": "*", "ext-json": "*", - "ext-mongodb": "^1.8.1", - "jean85/pretty-package-versions": "^1.2", - "php": "^7.0 || ^8.0", + "ext-mongodb": "^1.11.0", + "jean85/pretty-package-versions": "^1.2 || ^2.0.1", + "php": "^7.1 || ^8.0", "symfony/polyfill-php80": "^1.19" }, "require-dev": { - "squizlabs/php_codesniffer": "^3.5, <3.5.5", - "symfony/phpunit-bridge": "5.x-dev" + "doctrine/coding-standard": "^9.0", + "squizlabs/php_codesniffer": "^3.6", + "symfony/phpunit-bridge": "^5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -198,9 +130,9 @@ ], "support": { "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" }, - "time": "2020-11-25T12:26:02+00:00" + "time": "2021-10-20T22:22:37+00:00" }, { "name": "symfony/polyfill-php80", @@ -336,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "0.43.0", + "version": "0.44.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9" + "reference": "091e1a1f24a4d98657d8c03feb0565d6cf81d0cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/fb96fc6c94d5efcd43913c34bece62daba76a5e9", - "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9", + "url": "https://api.github.com/repos/utopia-php/database/zipball/091e1a1f24a4d98657d8c03feb0565d6cf81d0cb", + "reference": "091e1a1f24a4d98657d8c03feb0565d6cf81d0cb", "shasum": "" }, "require": { @@ -354,12 +286,11 @@ "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*", - "utopia-php/mongo": "0.2.*" + "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "^1.14", "laravel/pint": "1.4.*", - "mongodb/mongodb": "1.8.0", "pcov/clobber": "^2.0", "phpstan/phpstan": "1.10.*", "phpunit/phpunit": "^9.4", @@ -387,22 +318,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.43.0" + "source": "https://github.com/utopia-php/database/tree/0.44.0" }, - "time": "2023-08-29T10:18:39+00:00" + "time": "2023-10-17T03:35:00+00:00" }, { "name": "utopia-php/framework", - "version": "0.30.0", + "version": "0.31.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "0969d429997996ceade53e477fce31221b415651" + "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/0969d429997996ceade53e477fce31221b415651", - "reference": "0969d429997996ceade53e477fce31221b415651", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", + "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", "shasum": "" }, "require": { @@ -432,27 +363,27 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.30.0" + "source": "https://github.com/utopia-php/framework/tree/0.31.0" }, - "time": "2023-08-07T07:55:48+00:00" + "time": "2023-08-30T16:10:04+00:00" }, { "name": "utopia-php/mongo", - "version": "0.2.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09" + "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", - "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", + "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", "shasum": "" }, "require": { "ext-mongodb": "*", - "mongodb/mongodb": "1.8.0", + "mongodb/mongodb": "1.10.0", "php": ">=8.0" }, "require-dev": { @@ -492,9 +423,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.2.0" + "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, - "time": "2023-03-22T10:44:29+00:00" + "time": "2023-09-01T17:25:28+00:00" } ], "packages-dev": [ From 5b510848f149f1e2c25aa1f4a66e7d62ee0a140b Mon Sep 17 00:00:00 2001 From: Prateek Banga Date: Wed, 18 Oct 2023 12:42:42 +0530 Subject: [PATCH 212/470] chore bump db update --- composer.json | 2 +- composer.lock | 203 +++++++++++++++++--------------------------------- 2 files changed, 68 insertions(+), 137 deletions(-) diff --git a/composer.json b/composer.json index 9cf6e322782..f4fad25f1ce 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.43.*" + "utopia-php/database": "0.44.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 2ea78ab3017..5bb678f8795 100644 --- a/composer.lock +++ b/composer.lock @@ -4,101 +4,32 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f8bfbfa488c383d57b21f50b36546c2d", + "content-hash": "b26596d08cac61eb5aa80b96d8f550c5", "packages": [ - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, { "name": "jean85/pretty-package-versions", - "version": "1.6.0", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303" + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/1e0104b46f045868f11942aea058cd7186d6c303", - "reference": "1e0104b46f045868f11942aea058cd7186d6c303", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.8.0", - "php": "^7.0|^8.0" + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0|^8.5|^9.2" + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" }, "type": "library", "extra": { @@ -121,7 +52,7 @@ "email": "alessandro.lai85@gmail.com" } ], - "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "description": "A library to get pretty versions strings of installed dependencies", "keywords": [ "composer", "package", @@ -130,40 +61,41 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/1.6.0" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" }, - "time": "2021-02-04T16:20:16+00:00" + "time": "2021-10-08T21:21:46+00:00" }, { "name": "mongodb/mongodb", - "version": "1.8.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d" + "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/953dbc19443aa9314c44b7217a16873347e6840d", - "reference": "953dbc19443aa9314c44b7217a16873347e6840d", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", + "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", "shasum": "" }, "require": { "ext-hash": "*", "ext-json": "*", - "ext-mongodb": "^1.8.1", - "jean85/pretty-package-versions": "^1.2", - "php": "^7.0 || ^8.0", + "ext-mongodb": "^1.11.0", + "jean85/pretty-package-versions": "^1.2 || ^2.0.1", + "php": "^7.1 || ^8.0", "symfony/polyfill-php80": "^1.19" }, "require-dev": { - "squizlabs/php_codesniffer": "^3.5, <3.5.5", - "symfony/phpunit-bridge": "5.x-dev" + "doctrine/coding-standard": "^9.0", + "squizlabs/php_codesniffer": "^3.6", + "symfony/phpunit-bridge": "^5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -198,9 +130,9 @@ ], "support": { "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.8.0" + "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" }, - "time": "2020-11-25T12:26:02+00:00" + "time": "2021-10-20T22:22:37+00:00" }, { "name": "symfony/polyfill-php80", @@ -336,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "0.43.0", + "version": "0.44.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9" + "reference": "e0b832d217e4d429c96ade671e85ece942446543" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/fb96fc6c94d5efcd43913c34bece62daba76a5e9", - "reference": "fb96fc6c94d5efcd43913c34bece62daba76a5e9", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e0b832d217e4d429c96ade671e85ece942446543", + "reference": "e0b832d217e4d429c96ade671e85ece942446543", "shasum": "" }, "require": { @@ -354,12 +286,11 @@ "php": ">=8.0", "utopia-php/cache": "0.8.*", "utopia-php/framework": "0.*.*", - "utopia-php/mongo": "0.2.*" + "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "^1.14", "laravel/pint": "1.4.*", - "mongodb/mongodb": "1.8.0", "pcov/clobber": "^2.0", "phpstan/phpstan": "1.10.*", "phpunit/phpunit": "^9.4", @@ -387,22 +318,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.43.0" + "source": "https://github.com/utopia-php/database/tree/0.44.1" }, - "time": "2023-08-29T10:18:39+00:00" + "time": "2023-10-18T07:05:41+00:00" }, { "name": "utopia-php/framework", - "version": "0.30.0", + "version": "0.31.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "0969d429997996ceade53e477fce31221b415651" + "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/0969d429997996ceade53e477fce31221b415651", - "reference": "0969d429997996ceade53e477fce31221b415651", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", + "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", "shasum": "" }, "require": { @@ -432,27 +363,27 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.30.0" + "source": "https://github.com/utopia-php/framework/tree/0.31.0" }, - "time": "2023-08-07T07:55:48+00:00" + "time": "2023-08-30T16:10:04+00:00" }, { "name": "utopia-php/mongo", - "version": "0.2.0", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09" + "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", - "reference": "b6dfb31b93c07c59b8bbd62a3b52e3b97a407c09", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", + "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", "shasum": "" }, "require": { "ext-mongodb": "*", - "mongodb/mongodb": "1.8.0", + "mongodb/mongodb": "1.10.0", "php": ">=8.0" }, "require-dev": { @@ -492,9 +423,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.2.0" + "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, - "time": "2023-03-22T10:44:29+00:00" + "time": "2023-09-01T17:25:28+00:00" } ], "packages-dev": [ @@ -862,16 +793,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.32", + "version": "1.10.39", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", - "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", "shasum": "" }, "require": { @@ -920,20 +851,20 @@ "type": "tidelift" } ], - "time": "2023-08-24T21:54:50+00:00" + "time": "2023-10-17T15:46:26+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.27", + "version": "9.2.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { @@ -990,7 +921,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -998,7 +929,7 @@ "type": "github" } ], - "time": "2023-07-26T13:44:30+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1243,16 +1174,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.11", + "version": "9.6.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", - "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", "shasum": "" }, "require": { @@ -1267,7 +1198,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -1326,7 +1257,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" }, "funding": [ { @@ -1342,7 +1273,7 @@ "type": "tidelift" } ], - "time": "2023-08-19T07:10:56+00:00" + "time": "2023-09-19T05:39:22+00:00" }, { "name": "sebastian/cli-parser", @@ -2370,5 +2301,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From 0c5074565c672c7c8ca87026e1241422946b0066 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 1 Nov 2023 21:49:02 +1300 Subject: [PATCH 213/470] Update database --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index f4fad25f1ce..dca09d16e9a 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.44.*" + "utopia-php/database": "0.45.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 5bb678f8795..b81222f820a 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": "b26596d08cac61eb5aa80b96d8f550c5", + "content-hash": "7fed5f76d1e658061bf5ec6099ba3a60", "packages": [ { "name": "jean85/pretty-package-versions", @@ -268,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "0.44.1", + "version": "0.45.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e0b832d217e4d429c96ade671e85ece942446543" + "reference": "7dfb03e69306c406301b8b9655650ec56f09bf6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e0b832d217e4d429c96ade671e85ece942446543", - "reference": "e0b832d217e4d429c96ade671e85ece942446543", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7dfb03e69306c406301b8b9655650ec56f09bf6b", + "reference": "7dfb03e69306c406301b8b9655650ec56f09bf6b", "shasum": "" }, "require": { @@ -290,7 +290,7 @@ }, "require-dev": { "fakerphp/faker": "^1.14", - "laravel/pint": "1.4.*", + "laravel/pint": "1.13.*", "pcov/clobber": "^2.0", "phpstan/phpstan": "1.10.*", "phpunit/phpunit": "^9.4", @@ -318,9 +318,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.44.1" + "source": "https://github.com/utopia-php/database/tree/0.45.0" }, - "time": "2023-10-18T07:05:41+00:00" + "time": "2023-11-01T08:30:19+00:00" }, { "name": "utopia-php/framework", From 171eda04bfc53e5e24bdb36230ae84fe686ac2ee Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 22 Nov 2023 22:49:43 +1300 Subject: [PATCH 214/470] Ignore duplicate collection for shared table isolation modes --- composer.json | 2 +- composer.lock | 27 ++++++++++++++++++--------- src/Abuse/Adapters/TimeLimit.php | 8 ++++++-- tests/Abuse/AbuseTest.php | 2 +- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index dca09d16e9a..a0f581d3b44 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.45.*" + "utopia-php/database": "dev-feat-isolation-mode as 0.45.0" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index b81222f820a..cd9c98c99a3 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": "7fed5f76d1e658061bf5ec6099ba3a60", + "content-hash": "49f509e28dd71618101988ac78e30fa5", "packages": [ { "name": "jean85/pretty-package-versions", @@ -268,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "0.45.0", + "version": "dev-feat-isolation-mode", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7dfb03e69306c406301b8b9655650ec56f09bf6b" + "reference": "b7bcc9b373242a9e494b0b7bbf78dfa54333727c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7dfb03e69306c406301b8b9655650ec56f09bf6b", - "reference": "7dfb03e69306c406301b8b9655650ec56f09bf6b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b7bcc9b373242a9e494b0b7bbf78dfa54333727c", + "reference": "b7bcc9b373242a9e494b0b7bbf78dfa54333727c", "shasum": "" }, "require": { @@ -318,9 +318,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.45.0" + "source": "https://github.com/utopia-php/database/tree/feat-isolation-mode" }, - "time": "2023-11-01T08:30:19+00:00" + "time": "2023-11-22T07:58:21+00:00" }, { "name": "utopia-php/framework", @@ -2290,9 +2290,18 @@ "time": "2021-07-28T10:34:58+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-isolation-mode", + "alias": "0.45.0", + "alias_normalized": "0.45.0.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b33c1e4fc17..038f32c0dbe 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -69,7 +69,7 @@ public function __construct(string $key, int $limit, int $seconds, Database $db) */ public function setup(): void { - if (! $this->db->exists($this->db->getDefaultDatabase())) { + if (! $this->db->exists($this->db->getDatabase())) { throw new Exception('You need to create database before running timelimit setup'); } @@ -120,7 +120,11 @@ public function setup(): void ]), ]; - $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); + try { + $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); + } catch (Duplicate) { + // Collection already exists + } } /** diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index dac103dcc9c..755b6e6d677 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -37,7 +37,7 @@ public function setUp(): void $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPdoAttributes()); $db = new Database(new MySQL($pdo), new Cache(new NoCache())); - $db->setDefaultDatabase('utopiaTests'); + $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); $this->db = $db; From 2c54030c13e245647f7bdd9feb3bf72a34d813f6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 8 Dec 2023 17:28:47 +0100 Subject: [PATCH 215/470] Update database --- composer.json | 2 +- composer.lock | 27 +++++++++------------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index a0f581d3b44..c77a09db5b1 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "dev-feat-isolation-mode as 0.45.0" + "utopia-php/database": "0.46.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index cd9c98c99a3..5070a450071 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": "49f509e28dd71618101988ac78e30fa5", + "content-hash": "b6aaf867d22695bbe134151232701abd", "packages": [ { "name": "jean85/pretty-package-versions", @@ -268,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-isolation-mode", + "version": "0.46.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b7bcc9b373242a9e494b0b7bbf78dfa54333727c" + "reference": "035e6b4a0005ad87aa73beca8714721925ea8f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b7bcc9b373242a9e494b0b7bbf78dfa54333727c", - "reference": "b7bcc9b373242a9e494b0b7bbf78dfa54333727c", + "url": "https://api.github.com/repos/utopia-php/database/zipball/035e6b4a0005ad87aa73beca8714721925ea8f5e", + "reference": "035e6b4a0005ad87aa73beca8714721925ea8f5e", "shasum": "" }, "require": { @@ -318,9 +318,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-isolation-mode" + "source": "https://github.com/utopia-php/database/tree/0.46.0" }, - "time": "2023-11-22T07:58:21+00:00" + "time": "2023-12-08T16:02:38+00:00" }, { "name": "utopia-php/framework", @@ -2290,18 +2290,9 @@ "time": "2021-07-28T10:34:58+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-isolation-mode", - "alias": "0.45.0", - "alias_normalized": "0.45.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From 0240ee881ac9d4eade854267b7611684b8b71c74 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 15 Dec 2023 00:05:54 +1300 Subject: [PATCH 216/470] Update database --- composer.json | 2 +- composer.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index c77a09db5b1..9e085c0d21f 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.46.*" + "utopia-php/database": "0.47.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 5070a450071..87de045db86 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": "b6aaf867d22695bbe134151232701abd", + "content-hash": "52241d65c647a3ce918e4dc4d96e50e9", "packages": [ { "name": "jean85/pretty-package-versions", @@ -268,16 +268,16 @@ }, { "name": "utopia-php/database", - "version": "0.46.0", + "version": "0.47.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "035e6b4a0005ad87aa73beca8714721925ea8f5e" + "reference": "a87cc66ebea6686066e03c64867fc7db07fa80cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/035e6b4a0005ad87aa73beca8714721925ea8f5e", - "reference": "035e6b4a0005ad87aa73beca8714721925ea8f5e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/a87cc66ebea6686066e03c64867fc7db07fa80cf", + "reference": "a87cc66ebea6686066e03c64867fc7db07fa80cf", "shasum": "" }, "require": { @@ -318,22 +318,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.46.0" + "source": "https://github.com/utopia-php/database/tree/0.47.0" }, - "time": "2023-12-08T16:02:38+00:00" + "time": "2023-12-14T11:02:15+00:00" }, { "name": "utopia-php/framework", - "version": "0.31.0", + "version": "0.31.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", + "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", "shasum": "" }, "require": { @@ -363,9 +363,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.0" + "source": "https://github.com/utopia-php/framework/tree/0.31.1" }, - "time": "2023-08-30T16:10:04+00:00" + "time": "2023-12-08T18:47:29+00:00" }, { "name": "utopia-php/mongo", From db5f9364ea0962e066a3c8f35e681632b3e9a6ca Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 19 Jan 2024 22:23:41 +1300 Subject: [PATCH 217/470] Update database --- composer.json | 2 +- composer.lock | 141 ++++++++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 70 deletions(-) diff --git a/composer.json b/composer.json index 9e085c0d21f..0abd8508b3b 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.47.*" + "utopia-php/database": "0.48.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 87de045db86..41022b324fa 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": "52241d65c647a3ce918e4dc4d96e50e9", + "content-hash": "b36e240204ad0a32ebcfcaeca96a8d94", "packages": [ { "name": "jean85/pretty-package-versions", @@ -219,16 +219,16 @@ }, { "name": "utopia-php/cache", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "212e66100a1f32e674fca5d9bc317cc998303089" + "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/212e66100a1f32e674fca5d9bc317cc998303089", - "reference": "212e66100a1f32e674fca5d9bc317cc998303089", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", + "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", "shasum": "" }, "require": { @@ -239,6 +239,7 @@ }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -262,29 +263,29 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.8.0" + "source": "https://github.com/utopia-php/cache/tree/0.9.0" }, - "time": "2022-10-16T16:48:09+00:00" + "time": "2024-01-07T18:11:23+00:00" }, { "name": "utopia-php/database", - "version": "0.47.0", + "version": "0.48.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "a87cc66ebea6686066e03c64867fc7db07fa80cf" + "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/a87cc66ebea6686066e03c64867fc7db07fa80cf", - "reference": "a87cc66ebea6686066e03c64867fc7db07fa80cf", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2651f41b9d3909dc123d26becfb6a3a44fb63077", + "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/cache": "0.8.*", + "utopia-php/cache": "0.9.*", "utopia-php/framework": "0.*.*", "utopia-php/mongo": "0.3.*" }, @@ -318,22 +319,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.47.0" + "source": "https://github.com/utopia-php/database/tree/0.48.0" }, - "time": "2023-12-14T11:02:15+00:00" + "time": "2024-01-19T08:17:22+00:00" }, { "name": "utopia-php/framework", - "version": "0.31.1", + "version": "0.33.1", "source": { "type": "git", - "url": "https://github.com/utopia-php/framework.git", - "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68" + "url": "https://github.com/utopia-php/http.git", + "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", - "reference": "e50d2d16f4bc31319043f3f6d3dbea36c6fd6b68", + "url": "https://api.github.com/repos/utopia-php/http/zipball/b745607aa1875554a0ad52e28f6db918da1ce11c", + "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c", "shasum": "" }, "require": { @@ -362,10 +363,10 @@ "upf" ], "support": { - "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.1" + "issues": "https://github.com/utopia-php/http/issues", + "source": "https://github.com/utopia-php/http/tree/0.33.1" }, - "time": "2023-12-08T18:47:29+00:00" + "time": "2024-01-17T16:48:32+00:00" }, { "name": "utopia-php/mongo", @@ -626,25 +627,27 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -652,7 +655,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -676,9 +679,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2024-01-07T17:17:35+00:00" }, { "name": "phar-io/manifest", @@ -793,16 +796,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.39", + "version": "1.10.56", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4" + "reference": "27816a01aea996191ee14d010f325434c0ee76fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d9dedb0413f678b4d03cbc2279a48f91592c97c4", - "reference": "d9dedb0413f678b4d03cbc2279a48f91592c97c4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa", + "reference": "27816a01aea996191ee14d010f325434c0ee76fa", "shasum": "" }, "require": { @@ -851,27 +854,27 @@ "type": "tidelift" } ], - "time": "2023-10-17T15:46:26+00:00" + "time": "2024-01-15T10:43:00+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -921,7 +924,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -929,7 +932,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1174,16 +1177,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.13", + "version": "9.6.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", "shasum": "" }, "require": { @@ -1257,7 +1260,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" }, "funding": [ { @@ -1273,7 +1276,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:39:22+00:00" + "time": "2024-01-19T07:03:14+00:00" }, { "name": "sebastian/cli-parser", @@ -1518,20 +1521,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1563,7 +1566,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -1571,7 +1574,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -1845,20 +1848,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1890,7 +1893,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -1898,7 +1901,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", @@ -2241,16 +2244,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2279,7 +2282,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2287,7 +2290,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], From 5032d5220af527853e2d549e62be3f4ee2932db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 5 Mar 2024 09:31:14 +0000 Subject: [PATCH 218/470] Upgrade DB lib --- composer.json | 2 +- composer.lock | 208 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 127 insertions(+), 83 deletions(-) diff --git a/composer.json b/composer.json index 0abd8508b3b..29d77f51d5e 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.48.*" + "utopia-php/database": "0.49.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 41022b324fa..75d7d7a6b6c 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": "b36e240204ad0a32ebcfcaeca96a8d94", + "content-hash": "f78eeda391d225f378154fe10e288486", "packages": [ { "name": "jean85/pretty-package-versions", @@ -136,16 +136,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -153,9 +153,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -199,7 +196,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -215,7 +212,7 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "utopia-php/cache", @@ -269,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.48.0", + "version": "0.49.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077" + "reference": "02000f01e9329b92251825fdccde023feb88a915" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2651f41b9d3909dc123d26becfb6a3a44fb63077", - "reference": "2651f41b9d3909dc123d26becfb6a3a44fb63077", + "url": "https://api.github.com/repos/utopia-php/database/zipball/02000f01e9329b92251825fdccde023feb88a915", + "reference": "02000f01e9329b92251825fdccde023feb88a915", "shasum": "" }, "require": { @@ -286,7 +283,8 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.9.*", - "utopia-php/framework": "0.*.*", + "utopia-php/fetch": "0.1.*", + "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -319,22 +317,61 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.48.0" + "source": "https://github.com/utopia-php/database/tree/0.49.0" + }, + "time": "2024-03-01T10:44:41+00:00" + }, + { + "name": "utopia-php/fetch", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/fetch.git", + "reference": "2fa214b9262acd1a3583515a364da4f35929d5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/2fa214b9262acd1a3583515a364da4f35929d5c5", + "reference": "2fa214b9262acd1a3583515a364da4f35929d5c5", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "^1.5.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Fetch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple library that provides an interface for making HTTP Requests.", + "support": { + "issues": "https://github.com/utopia-php/fetch/issues", + "source": "https://github.com/utopia-php/fetch/tree/0.1.0" }, - "time": "2024-01-19T08:17:22+00:00" + "time": "2023-10-10T11:58:32+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.1", + "version": "0.33.2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c" + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/b745607aa1875554a0ad52e28f6db918da1ce11c", - "reference": "b745607aa1875554a0ad52e28f6db918da1ce11c", + "url": "https://api.github.com/repos/utopia-php/http/zipball/b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", "shasum": "" }, "require": { @@ -364,9 +401,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.1" + "source": "https://github.com/utopia-php/http/tree/0.33.2" }, - "time": "2024-01-17T16:48:32+00:00" + "time": "2024-01-31T10:35:59+00:00" }, { "name": "utopia-php/mongo", @@ -627,16 +664,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.0", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", "shasum": "" }, "require": { @@ -679,26 +716,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" }, - "time": "2024-01-07T17:17:35+00:00" + "time": "2024-02-21T19:24:10+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -739,9 +777,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -796,16 +840,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.56", + "version": "1.10.59", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "27816a01aea996191ee14d010f325434c0ee76fa" + "reference": "e607609388d3a6d418a50a49f7940e8086798281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa", - "reference": "27816a01aea996191ee14d010f325434c0ee76fa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281", + "reference": "e607609388d3a6d418a50a49f7940e8086798281", "shasum": "" }, "require": { @@ -854,20 +898,20 @@ "type": "tidelift" } ], - "time": "2024-01-15T10:43:00+00:00" + "time": "2024-02-20T13:59:13+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.30", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", - "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -924,7 +968,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -932,7 +976,7 @@ "type": "github" } ], - "time": "2023-12-22T06:47:57+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1177,16 +1221,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.16", + "version": "9.6.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", - "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", + "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", "shasum": "" }, "require": { @@ -1260,7 +1304,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" }, "funding": [ { @@ -1276,20 +1320,20 @@ "type": "tidelift" } ], - "time": "2024-01-19T07:03:14+00:00" + "time": "2024-02-23T13:14:51+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { @@ -1324,7 +1368,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" }, "funding": [ { @@ -1332,7 +1376,7 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-03-02T06:27:43+00:00" }, { "name": "sebastian/code-unit", @@ -1578,16 +1622,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "shasum": "" }, "require": { @@ -1632,7 +1676,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" }, "funding": [ { @@ -1640,7 +1684,7 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2024-03-02T06:30:58+00:00" }, { "name": "sebastian/environment", @@ -1707,16 +1751,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "shasum": "" }, "require": { @@ -1772,7 +1816,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -1780,20 +1824,20 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { @@ -1836,7 +1880,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -1844,7 +1888,7 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { "name": "sebastian/lines-of-code", @@ -2244,16 +2288,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -2282,7 +2326,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -2290,7 +2334,7 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], From 02a09117df7c8dfa9cbd29d4c76a07081714a155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 6 Mar 2024 16:02:21 +0000 Subject: [PATCH 219/470] Update lockfile --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 75d7d7a6b6c..4179b93ab48 100644 --- a/composer.lock +++ b/composer.lock @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.49.0", + "version": "0.49.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "02000f01e9329b92251825fdccde023feb88a915" + "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/02000f01e9329b92251825fdccde023feb88a915", - "reference": "02000f01e9329b92251825fdccde023feb88a915", + "url": "https://api.github.com/repos/utopia-php/database/zipball/4199fe8f00f4e181c7782c4a6862845d591c1f03", + "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03", "shasum": "" }, "require": { @@ -317,9 +317,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.0" + "source": "https://github.com/utopia-php/database/tree/0.49.1" }, - "time": "2024-03-01T10:44:41+00:00" + "time": "2024-03-06T11:35:53+00:00" }, { "name": "utopia-php/fetch", @@ -664,16 +664,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.1", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", - "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { @@ -716,9 +716,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2024-02-21T19:24:10+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "phar-io/manifest", From 55b34b581c957fc98f912943d94dcdc7079f191e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Fri, 8 Mar 2024 11:27:58 +0000 Subject: [PATCH 220/470] Introduce auth instance --- composer.json | 2 +- composer.lock | 78 ++++++++++++++++++-------------- src/Abuse/Adapters/TimeLimit.php | 15 +++--- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index 29d77f51d5e..dc3e6c0cb1e 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.49.*" + "utopia-php/database": "dev-feat-framework-v2 as 0.49.99" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 4179b93ab48..9c8ec72f89a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f78eeda391d225f378154fe10e288486", + "content-hash": "1b7e75223ec175933d427857707d8c4c", "packages": [ { "name": "jean85/pretty-package-versions", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", "shasum": "" }, "require": { @@ -25,9 +25,9 @@ "php": "^7.1|^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^0.12.66", + "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^7.5|^8.5|^9.4", "vimeo/psalm": "^4.3" }, @@ -61,9 +61,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" }, - "time": "2021-10-08T21:21:46+00:00" + "time": "2024-03-08T09:58:59+00:00" }, { "name": "mongodb/mongodb", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.49.1", + "version": "dev-feat-framework-v2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03" + "reference": "bdd9140e40c77faadb0cf2f5050b466c34eef673" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4199fe8f00f4e181c7782c4a6862845d591c1f03", - "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03", + "url": "https://api.github.com/repos/utopia-php/database/zipball/bdd9140e40c77faadb0cf2f5050b466c34eef673", + "reference": "bdd9140e40c77faadb0cf2f5050b466c34eef673", "shasum": "" }, "require": { @@ -284,7 +284,7 @@ "php": ">=8.0", "utopia-php/cache": "0.9.*", "utopia-php/fetch": "0.1.*", - "utopia-php/framework": "0.33.*", + "utopia-php/framework": "0.34.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -295,7 +295,7 @@ "phpunit/phpunit": "^9.4", "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.14.0" + "utopia-php/cli": "0.17.*" }, "type": "library", "autoload": { @@ -317,9 +317,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.1" + "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" }, - "time": "2024-03-06T11:35:53+00:00" + "time": "2024-03-07T16:55:44+00:00" }, { "name": "utopia-php/fetch", @@ -362,48 +362,51 @@ }, { "name": "utopia-php/framework", - "version": "0.33.2", + "version": "0.34.2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3" + "reference": "fd126c02b78cc80678c9638f7b335dfb4a841b78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", - "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", + "url": "https://api.github.com/repos/utopia-php/http/zipball/fd126c02b78cc80678c9638f7b335dfb4a841b78", + "reference": "fd126c02b78cc80678c9638f7b335dfb4a841b78", "shasum": "" }, "require": { + "ext-swoole": "*", "php": ">=8.0" }, "require-dev": { "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\Http\\": "src/Http" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A simple, light and advanced PHP framework", + "description": "A simple, light and advanced PHP HTTP framework", "keywords": [ "framework", + "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.2" + "source": "https://github.com/utopia-php/http/tree/0.34.2" }, - "time": "2024-01-31T10:35:59+00:00" + "time": "2024-02-20T11:36:56+00:00" }, { "name": "utopia-php/mongo", @@ -840,16 +843,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.59", + "version": "1.10.60", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e607609388d3a6d418a50a49f7940e8086798281" + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281", - "reference": "e607609388d3a6d418a50a49f7940e8086798281", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", "shasum": "" }, "require": { @@ -898,7 +901,7 @@ "type": "tidelift" } ], - "time": "2024-02-20T13:59:13+00:00" + "time": "2024-03-07T13:30:19+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2337,9 +2340,18 @@ "time": "2024-03-03T12:36:25+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-framework-v2", + "alias": "0.49.99", + "alias_normalized": "0.49.99.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 038f32c0dbe..4ce97ca9ab3 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -48,14 +48,17 @@ class TimeLimit implements Adapter */ protected array $params = []; + protected Authorization $auth; + /** * @param string $key * @param int $seconds * @param int $limit * @param Database $db */ - public function __construct(string $key, int $limit, int $seconds, Database $db) + public function __construct(string $key, int $limit, int $seconds, Database $db, Authorization $auth) { + $this->auth = $auth; $this->key = $key; $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); @@ -70,7 +73,7 @@ public function __construct(string $key, int $limit, int $seconds, Database $db) public function setup(): void { if (! $this->db->exists($this->db->getDatabase())) { - throw new Exception('You need to create database before running timelimit setup'); + throw new \Exception('You need to create database before running timelimit setup'); } $attributes = [ @@ -191,7 +194,7 @@ protected function count(string $key, string $datetime): int } /** @var array $result */ - $result = Authorization::skip(function () use ($key, $datetime) { + $result = $this->auth->skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -223,7 +226,7 @@ protected function hit(string $key, string $datetime): void return; } - Authorization::skip(function () use ($datetime, $key) { + $this->auth->skip(function () use ($datetime, $key) { $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -280,7 +283,7 @@ protected function hit(string $key, string $datetime): void public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ - $results = Authorization::skip(function () use ($offset, $limit) { + $results = $this->auth->skip(function () use ($offset, $limit) { $queries = []; $queries[] = Query::orderDesc(''); @@ -307,7 +310,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(string $datetime): bool { - Authorization::skip(function () use ($datetime) { + $this->auth->skip(function () use ($datetime) { do { $documents = $this->db->find(TimeLimit::COLLECTION, [ Query::lessThan('time', $datetime), From 6f44bd8acd13613e2c39c005dc8f66de73ade025 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 12 Mar 2024 07:42:37 +0000 Subject: [PATCH 221/470] abuse adapter using redis --- Dockerfile | 17 +++ composer.json | 1 + docker-compose.yml | 8 +- src/Abuse/Adapters/Redis.php | 184 +++++++++++++++++++++++++++++++++ tests/Abuse/AbuseRedisTest.php | 85 +++++++++++++++ 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/Abuse/Adapters/Redis.php create mode 100644 tests/Abuse/AbuseRedisTest.php diff --git a/Dockerfile b/Dockerfile index 2fda963e65d..7873556ff08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,23 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql +RUN \ + apk update \ + && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ + && rm -rf /var/cache/apk/* + +RUN \ + # Redis Extension + git clone https://github.com/phpredis/phpredis.git && \ + cd phpredis && \ + git checkout $PHP_REDIS_VERSION && \ + phpize && \ + ./configure && \ + make && make install && \ + cd .. + +RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini + WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/composer.json b/composer.json index 29d77f51d5e..d5513a3b9bd 100755 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", + "ext-redis": "*", "utopia-php/database": "0.49.*" }, "require-dev": { diff --git a/docker-compose.yml b/docker-compose.yml index c0c6f77b996..c9a509d40a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,13 @@ services: - abuse ports: - "9307:3306" - + + redis: + image: redis:6.0-alpine + container_name: redis + networks: + - abuse + tests: build: context: . diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php new file mode 100644 index 00000000000..da4dc6115a4 --- /dev/null +++ b/src/Abuse/Adapters/Redis.php @@ -0,0 +1,184 @@ + + */ + protected array $params = []; + + + public function __construct(string $key, int $limit, int $seconds, Client $redis) + { + $this->redis = $redis; + $this->key = $key; + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $this->time = $time; + $this->limit = $limit; + } + + /** + * Set Param + * + * Set custom param for key pattern parsing + * + * @param string $key + * @param string $value + * @return $this + */ + public function setParam(string $key, string $value): self + { + $this->params[$key] = $value; + + return $this; + } + + /** + * Get Params + * + * Return array of all key params + * + * @return array + */ + protected function getParams(): array + { + return $this->params; + } + + /** + * Parse key with all custom attached params + * + * @return string + */ + protected function parseKey(): string + { + foreach ($this->getParams() as $key => $value) { + $this->key = \str_replace($key, $value, $this->key); + } + + return $this->key; + } + + protected function count(string $key, string $datetime): int + { + if (0 == $this->limit) { // No limit no point for counting + return 0; + } + + if (! \is_null($this->count)) { // Get fetched result + return $this->count; + } + + $this->count = $this->redis->get(self::NAMESPACE . ':' . $key .':' . $datetime); + + return $this->count; + } + + /** + * @param string $key + * @param string $datetime + * @return void + * + */ + protected function hit(string $key, string $datetime): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $this->count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime) ?? 0; + + $this->redis->incr(self::NAMESPACE . ':'. $key .':'. $datetime); + $this->count++; + } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit + * + * @return bool + */ + public function check(): bool + { + if (0 == $this->limit) { + return false; + } + + $key = $this->parseKey(); + + if ($this->limit > $this->count($key, $this->time)) { + $this->hit($key, $this->time); + + return false; + } + + return true; + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + // TODO limit potential is SCAN but needs cursor no offset + return $this->redis->keys(self::NAMESPACE . ':*'); + } + + /** + * Delete all logs older than $datetime + * + * @param string $datetime + * @return bool + */ + public function cleanup(string $datetime): bool + { + // TODO + $iterator = NULL; + while($iterator !== 0) { + $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:' . $datetime); + var_dump($keys); + $this->redis->del($keys); + } + return true; + } + +} \ No newline at end of file diff --git a/tests/Abuse/AbuseRedisTest.php b/tests/Abuse/AbuseRedisTest.php new file mode 100644 index 00000000000..7087566015e --- /dev/null +++ b/tests/Abuse/AbuseRedisTest.php @@ -0,0 +1,85 @@ +redis = new Client(); + $this->redis->connect('redis', 6379); + $adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $this->abuse = new Abuse($adapter); + } + + public function tearDown(): void + { + unset($this->abuse); + } + + public function testImitate2Requests(): void + { + $key = '{{ip}}'; + $value = '0.0.0.10'; + + $adapter = new Redis($key, 1, 1, $this->redis); + $adapter->setParam($key, $value); + $this->abuseIp = new Abuse($adapter); + $this->assertEquals($this->abuseIp->check(), false); + $this->assertEquals($this->abuseIp->check(), true); + + sleep(1); + + $adapter = new Redis($key, 1, 1, $this->redis); + $adapter->setParam($key, $value); + $this->abuseIp = new Abuse($adapter); + + $this->assertEquals($this->abuseIp->check(), false); + $this->assertEquals($this->abuseIp->check(), true); + } + + public function testIsValid(): void + { + // Use vars to resolve adapter key + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), false); + $this->assertEquals($this->abuse->check(), true); + } + + public function testCleanup(): void + { + // Check that there is only one log + $logs = $this->abuse->getLogs(0, 10); + $this->assertEquals(3, \count($logs)); + + // Delete the log + $interval = DateInterval::createFromDateString(1 . ' seconds'); + $status = $this->abuse->cleanup((new \DateTime())->sub($interval)->getTimestamp()); + $this->assertEquals($status, true); + + // Check that there are no logs in the DB + $logs = $this->abuse->getLogs(0, 10); + $this->assertEquals(0, \count($logs)); + } +} From 126cadf31af1d2901d62bc3012c5e988214a32ce Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 12 Mar 2024 07:51:13 +0000 Subject: [PATCH 222/470] fix cleanup --- src/Abuse/Adapters/Redis.php | 17 +++++++++++++++-- tests/Abuse/AbuseRedisTest.php | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index da4dc6115a4..e2ebe65f648 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -174,11 +174,24 @@ public function cleanup(string $datetime): bool // TODO $iterator = NULL; while($iterator !== 0) { - $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:' . $datetime); - var_dump($keys); + $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:*', 1000); + $keys = $this->filterKeys($keys, $datetime); $this->redis->del($keys); } return true; } + protected function filterKeys(array $keys, int $timestamp): array { + $filteredKeys = []; + foreach ($keys as $key) { + $parts = explode(':', $key); + $keyTimestamp = (int)end($parts); // Assuming the last part is always the timestamp + if ($keyTimestamp < $timestamp) { + $filteredKeys[] = $key; + } + } + return $filteredKeys; + } + + } \ No newline at end of file diff --git a/tests/Abuse/AbuseRedisTest.php b/tests/Abuse/AbuseRedisTest.php index 7087566015e..aecedb3209f 100644 --- a/tests/Abuse/AbuseRedisTest.php +++ b/tests/Abuse/AbuseRedisTest.php @@ -73,6 +73,7 @@ public function testCleanup(): void $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(3, \count($logs)); + sleep(5); // Delete the log $interval = DateInterval::createFromDateString(1 . ' seconds'); $status = $this->abuse->cleanup((new \DateTime())->sub($interval)->getTimestamp()); From ed30e8a74fa0f34c360fcca2593e3b08b2ce84f0 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 12 Mar 2024 07:51:25 +0000 Subject: [PATCH 223/470] fix format --- src/Abuse/Adapters/Redis.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index e2ebe65f648..63d162ffc0e 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -160,7 +160,7 @@ public function check(): bool public function getLogs(?int $offset = null, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset - return $this->redis->keys(self::NAMESPACE . ':*'); + return $this->redis->keys(self::NAMESPACE . ':*'); } /** @@ -172,8 +172,8 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array public function cleanup(string $datetime): bool { // TODO - $iterator = NULL; - while($iterator !== 0) { + $iterator = null; + while ($iterator !== 0) { $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:*', 1000); $keys = $this->filterKeys($keys, $datetime); $this->redis->del($keys); @@ -181,7 +181,8 @@ public function cleanup(string $datetime): bool return true; } - protected function filterKeys(array $keys, int $timestamp): array { + protected function filterKeys(array $keys, int $timestamp): array + { $filteredKeys = []; foreach ($keys as $key) { $parts = explode(':', $key); @@ -192,6 +193,4 @@ protected function filterKeys(array $keys, int $timestamp): array { } return $filteredKeys; } - - -} \ No newline at end of file +} From 9bfe9136e6ecc13bcc1b1fd4ca83a6a2f2f5f4e7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 12 Mar 2024 08:03:23 +0000 Subject: [PATCH 224/470] fix code analysis errors --- src/Abuse/Adapters/Redis.php | 52 ++++++++++++++++++++++++++++------ tests/Abuse/AbuseRedisTest.php | 3 +- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index 63d162ffc0e..977fb0943e8 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -20,9 +20,9 @@ class Redis implements Adapter protected string $key = ''; /** - * @var string + * @var int */ - protected string $time; + protected int $time; /** * @var int @@ -91,7 +91,14 @@ protected function parseKey(): string return $this->key; } - protected function count(string $key, string $datetime): int + /** + * Undocumented function + * + * @param string $key + * @param int $datetime + * @return integer + */ + protected function count(string $key, int $datetime): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -101,24 +108,34 @@ protected function count(string $key, string $datetime): int return $this->count; } - $this->count = $this->redis->get(self::NAMESPACE . ':' . $key .':' . $datetime); + $count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime); + if (!$count) { + $this->count = 0; + } else { + $this->count = (int) $count; + } return $this->count; } /** * @param string $key - * @param string $datetime + * @param int $datetime * @return void * */ - protected function hit(string $key, string $datetime): void + protected function hit(string $key, int $datetime): void { if (0 == $this->limit) { // No limit no point for counting return; } - $this->count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime) ?? 0; + $count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime); + if (!$count) { + $this->count = 0; + } else { + $this->count = (int) $count; + } $this->redis->incr(self::NAMESPACE . ':'. $key .':'. $datetime); $this->count++; @@ -160,7 +177,17 @@ public function check(): bool public function getLogs(?int $offset = null, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset - return $this->redis->keys(self::NAMESPACE . ':*'); + $cursor = null; + $keys = $this->redis->scan($cursor, self::NAMESPACE . ':*', $limit); + if (!$keys) { + return []; + } + + $logs = []; + foreach ($keys as $key) { + $logs[$key] = $this->redis->get($key); + } + return $logs; } /** @@ -175,12 +202,19 @@ public function cleanup(string $datetime): bool $iterator = null; while ($iterator !== 0) { $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:*', 1000); - $keys = $this->filterKeys($keys, $datetime); + $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); $this->redis->del($keys); } return true; } + /** + * Filter keys + * + * @param array $keys + * @param integer $timestamp + * @return array + */ protected function filterKeys(array $keys, int $timestamp): array { $filteredKeys = []; diff --git a/tests/Abuse/AbuseRedisTest.php b/tests/Abuse/AbuseRedisTest.php index aecedb3209f..6498dc9fd8a 100644 --- a/tests/Abuse/AbuseRedisTest.php +++ b/tests/Abuse/AbuseRedisTest.php @@ -76,7 +76,8 @@ public function testCleanup(): void sleep(5); // Delete the log $interval = DateInterval::createFromDateString(1 . ' seconds'); - $status = $this->abuse->cleanup((new \DateTime())->sub($interval)->getTimestamp()); + $timestamp = (new \DateTime())->sub($interval)->getTimestamp(); + $status = $this->abuse->cleanup(strval($timestamp)); $this->assertEquals($status, true); // Check that there are no logs in the DB From fdb39d5800868ac77c886b9d4d1f65bbb50c11b5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 13 Mar 2024 02:09:53 +0000 Subject: [PATCH 225/470] refactor and tests --- src/Abuse/Adapter.php | 60 ++++++++++- src/Abuse/Adapters/ReCaptcha.php | 2 +- src/Abuse/Adapters/Redis.php | 55 +--------- src/Abuse/Adapters/TimeLimit.php | 54 +--------- tests/Abuse/AbuseTest.php | 104 ------------------- tests/Abuse/{AbuseRedisTest.php => Base.php} | 33 ++---- tests/Abuse/RedisTest.php | 39 +++++++ tests/Abuse/TimeLimitTest.php | 59 +++++++++++ 8 files changed, 165 insertions(+), 241 deletions(-) delete mode 100755 tests/Abuse/AbuseTest.php rename tests/Abuse/{AbuseRedisTest.php => Base.php} (63%) create mode 100644 tests/Abuse/RedisTest.php create mode 100755 tests/Abuse/TimeLimitTest.php diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 0cf1d280e12..4af27dc40a3 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -2,8 +2,18 @@ namespace Utopia\Abuse; -interface Adapter +abstract class Adapter { + /** + * @var array + */ + protected array $params = []; + + /** + * @var string + */ + protected string $key = ''; + /** * Check * @@ -11,7 +21,49 @@ interface Adapter * * @return bool */ - public function check(): bool; + abstract public function check(): bool; + + /** + * Set Param + * + * Set custom param for key pattern parsing + * + * @param string $key + * @param string $value + * @return $this + */ + public function setParam(string $key, string $value): self + { + $this->params[$key] = $value; + + return $this; + } + + /** + * Get Params + * + * Return array of all key params + * + * @return array + */ + protected function getParams(): array + { + return $this->params; + } + + /** + * Parse key with all custom attached params + * + * @return string + */ + protected function parseKey(): string + { + foreach ($this->getParams() as $key => $value) { + $this->key = \str_replace($key, $value, $this->key); + } + + return $this->key; + } /** * Get abuse logs @@ -22,7 +74,7 @@ public function check(): bool; * @param int|null $limit * @return array */ - public function getLogs(?int $offset = null, ?int $limit = 25): array; + abstract public function getLogs(?int $offset = null, ?int $limit = 25): array; /** * Delete all logs older than $datetime @@ -30,5 +82,5 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array; * @param string $datetime * @return bool */ - public function cleanup(string $datetime): bool; + abstract public function cleanup(string $datetime): bool; } diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 6f8a1797c94..ae117ac5b93 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -5,7 +5,7 @@ use Exception; use Utopia\Abuse\Adapter; -class ReCaptcha implements Adapter +class ReCaptcha extends Adapter { /** * Use this for communication between your site and Google. diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index 977fb0943e8..ea1ab0208d3 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -5,7 +5,7 @@ use Utopia\Abuse\Adapter; use Redis as Client; -class Redis implements Adapter +class Redis extends Adapter { public const NAMESPACE = 'abuse'; @@ -14,11 +14,6 @@ class Redis implements Adapter */ protected Client $redis; - /** - * @var string - */ - protected string $key = ''; - /** * @var int */ @@ -34,12 +29,6 @@ class Redis implements Adapter */ protected ?int $count = null; - /** - * @var array - */ - protected array $params = []; - - public function __construct(string $key, int $limit, int $seconds, Client $redis) { $this->redis = $redis; @@ -49,48 +38,6 @@ public function __construct(string $key, int $limit, int $seconds, Client $redis $this->limit = $limit; } - /** - * Set Param - * - * Set custom param for key pattern parsing - * - * @param string $key - * @param string $value - * @return $this - */ - public function setParam(string $key, string $value): self - { - $this->params[$key] = $value; - - return $this; - } - - /** - * Get Params - * - * Return array of all key params - * - * @return array - */ - protected function getParams(): array - { - return $this->params; - } - - /** - * Parse key with all custom attached params - * - * @return string - */ - protected function parseKey(): string - { - foreach ($this->getParams() as $key => $value) { - $this->key = \str_replace($key, $value, $this->key); - } - - return $this->key; - } - /** * Undocumented function * diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 038f32c0dbe..f654c98acbc 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -14,7 +14,7 @@ use Utopia\Database\Validator\Authorization; use Utopia\Exception; -class TimeLimit implements Adapter +class TimeLimit extends Adapter { public const COLLECTION = 'abuse'; @@ -23,11 +23,6 @@ class TimeLimit implements Adapter */ protected Database $db; - /** - * @var string - */ - protected string $key = ''; - /** * @var string */ @@ -43,11 +38,6 @@ class TimeLimit implements Adapter */ protected ?int $count = null; - /** - * @var array - */ - protected array $params = []; - /** * @param string $key * @param int $seconds @@ -127,48 +117,6 @@ public function setup(): void } } - /** - * Set Param - * - * Set custom param for key pattern parsing - * - * @param string $key - * @param string $value - * @return $this - */ - public function setParam(string $key, string $value): self - { - $this->params[$key] = $value; - - return $this; - } - - /** - * Get Params - * - * Return array of all key params - * - * @return array - */ - protected function getParams(): array - { - return $this->params; - } - - /** - * Parse key with all custom attached params - * - * @return string - */ - protected function parseKey(): string - { - foreach ($this->getParams() as $key => $value) { - $this->key = \str_replace($key, $value, $this->key); - } - - return $this->key; - } - /** * Check * diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php deleted file mode 100755 index 755b6e6d677..00000000000 --- a/tests/Abuse/AbuseTest.php +++ /dev/null @@ -1,104 +0,0 @@ -setDatabase('utopiaTests'); - $db->setNamespace('namespace'); - $this->db = $db; - - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); - if (! $db->exists('utopiaTests')) { - $db->create(); - $adapter->setup(); - } - - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); - } - - public function tearDown(): void - { - unset($this->abuse); - } - - public function testImitate2Requests(): void - { - $key = '{{ip}}'; - $value = '0.0.0.10'; - - $adapter = new TimeLimit($key, 1, 1, $this->db); - $adapter->setParam($key, $value); - $this->abuseIp = new Abuse($adapter); - $this->assertEquals($this->abuseIp->check(), false); - $this->assertEquals($this->abuseIp->check(), true); - - sleep(1); - - $adapter = new TimeLimit($key, 1, 1, $this->db); - $adapter->setParam($key, $value); - $this->abuseIp = new Abuse($adapter); - - $this->assertEquals($this->abuseIp->check(), false); - $this->assertEquals($this->abuseIp->check(), true); - } - - public function testIsValid(): void - { - // Use vars to resolve adapter key - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), true); - } - - public function testCleanup(): void - { - // Check that there is only one log - $logs = $this->abuse->getLogs(0, 10); - $this->assertEquals(3, \count($logs)); - - sleep(5); - // Delete the log - $status = $this->abuse->cleanup(DateTime::addSeconds(new \DateTime(), -1)); - $this->assertEquals($status, true); - - // Check that there are no logs in the DB - $logs = $this->abuse->getLogs(0, 10); - $this->assertEquals(0, \count($logs)); - } -} diff --git a/tests/Abuse/AbuseRedisTest.php b/tests/Abuse/Base.php similarity index 63% rename from tests/Abuse/AbuseRedisTest.php rename to tests/Abuse/Base.php index 6498dc9fd8a..1b88f411f61 100644 --- a/tests/Abuse/AbuseRedisTest.php +++ b/tests/Abuse/Base.php @@ -2,35 +2,19 @@ namespace Utopia\Tests; -use DateInterval; use PHPUnit\Framework\TestCase; use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\Redis; -use Redis as Client; -use Utopia\Exception; +use Utopia\Abuse\Adapter; -class AbuseRedisTest extends TestCase +abstract class Base extends TestCase { protected Abuse $abuse; protected Abuse $abuseIp; - protected Client $redis; + abstract public function getAdapter(string $key, int $limit, int $seconds): Adapter; - protected string $format = 'Y-m-d H:i:s.v'; - - /** - * @throws Exception - * @throws \Exception - */ - public function setUp(): void - { - $this->redis = new Client(); - $this->redis->connect('redis', 6379); - $adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); - } + abstract public function getCleanupDateTime(): string; public function tearDown(): void { @@ -42,7 +26,7 @@ public function testImitate2Requests(): void $key = '{{ip}}'; $value = '0.0.0.10'; - $adapter = new Redis($key, 1, 1, $this->redis); + $adapter = $this->getAdapter($key, 1, 1); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); @@ -50,7 +34,7 @@ public function testImitate2Requests(): void sleep(1); - $adapter = new Redis($key, 1, 1, $this->redis); + $adapter = $this->getAdapter($key, 1, 1); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); @@ -75,9 +59,8 @@ public function testCleanup(): void sleep(5); // Delete the log - $interval = DateInterval::createFromDateString(1 . ' seconds'); - $timestamp = (new \DateTime())->sub($interval)->getTimestamp(); - $status = $this->abuse->cleanup(strval($timestamp)); + + $status = $this->abuse->cleanup($this->getCleanupDateTime()); $this->assertEquals($status, true); // Check that there are no logs in the DB diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php new file mode 100644 index 00000000000..b29b5680852 --- /dev/null +++ b/tests/Abuse/RedisTest.php @@ -0,0 +1,39 @@ +redis = new Client(); + $this->redis->connect('redis', 6379); + $adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $this->abuse = new Abuse($adapter); + } + + public function getAdapter(string $key, int $limit, int $seconds): Adapter + { + return new Redis($key, $limit, $seconds, $this->redis); + } + + public function getCleanupDateTime(): string + { + $interval = DateInterval::createFromDateString(1 . ' seconds'); + return strval((new \DateTime())->sub($interval)->getTimestamp()); + } +} diff --git a/tests/Abuse/TimeLimitTest.php b/tests/Abuse/TimeLimitTest.php new file mode 100755 index 00000000000..495dae91376 --- /dev/null +++ b/tests/Abuse/TimeLimitTest.php @@ -0,0 +1,59 @@ +setDatabase('utopiaTests'); + $db->setNamespace('namespace'); + $this->db = $db; + + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + if (! $db->exists('utopiaTests')) { + $db->create(); + $adapter->setup(); + } + + $adapter->setParam('{{ip}}', '127.0.0.1'); + $this->abuse = new Abuse($adapter); + } + + public function getAdapter(string $key, int $limit, int $seconds): Adapter + { + return new TimeLimit($key, $limit, $seconds, $this->db); + } + + public function getCleanupDateTime(): string + { + return DateTime::addSeconds(new \DateTime(), -1); + } +} From 304c4d5ff8fe9b0915fd7da864192dbff580418f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 24 Mar 2024 06:47:18 +0000 Subject: [PATCH 226/470] support multiple php version for test --- .github/workflows/tests.yml | 6 ++++++ Dockerfile => Dockerfile.php-8.1 | 2 +- Dockerfile.php-8.2 | 26 ++++++++++++++++++++++++++ Dockerfile.php-8.3 | 26 ++++++++++++++++++++++++++ docker-compose.yml | 2 +- 5 files changed, 60 insertions(+), 2 deletions(-) rename Dockerfile => Dockerfile.php-8.1 (93%) create mode 100644 Dockerfile.php-8.2 create mode 100644 Dockerfile.php-8.3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31158bece78..19df1a72582 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,9 @@ jobs: lint: name: Tests runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['8.1', '8.2', '8.3'] steps: - name: Checkout repository @@ -14,6 +17,9 @@ jobs: - run: git checkout HEAD^2 + - name: Set PHP version env + run: export PHP_VERSION=${{ matrix.php-versions }} + - name: Build run: | docker compose build diff --git a/Dockerfile b/Dockerfile.php-8.1 similarity index 93% rename from Dockerfile rename to Dockerfile.php-8.1 index 2fda963e65d..df1a3073d07 100644 --- a/Dockerfile +++ b/Dockerfile.php-8.1 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM php:8.0-cli-alpine as final +FROM php:8.1-cli-alpine as final LABEL maintainer="team@appwrite.io" diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 new file mode 100644 index 00000000000..74b2276598e --- /dev/null +++ b/Dockerfile.php-8.2 @@ -0,0 +1,26 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY composer.lock /src/ +COPY composer.json /src/ + +RUN composer install --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM php:8.2-cli-alpine as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml + +CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 new file mode 100644 index 00000000000..8e8fc44e914 --- /dev/null +++ b/Dockerfile.php-8.3 @@ -0,0 +1,26 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY composer.lock /src/ +COPY composer.json /src/ + +RUN composer install --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM php:8.3-cli-alpine as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml + +CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c0c6f77b996..d9e4d1e2a46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: tests: build: context: . - dockerfile: ./Dockerfile + dockerfile: Dockerfile.php-$PHP_VERSION networks: - abuse depends_on: From c167c67b4ad773fc5543c17750f2bc533ea11d66 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 24 Mar 2024 06:48:36 +0000 Subject: [PATCH 227/470] fix script --- .github/workflows/tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 19df1a72582..8fa5b551af9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,11 +17,9 @@ jobs: - run: git checkout HEAD^2 - - name: Set PHP version env - run: export PHP_VERSION=${{ matrix.php-versions }} - - name: Build run: | + export PHP_VERSION=${{ matrix.php-versions }} docker compose build docker compose up -d sleep 10 From 2a1e279a775fe05c2ddb7cb742496f13d8dd0d57 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 24 Mar 2024 07:14:05 +0000 Subject: [PATCH 228/470] give default value for local test --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d9e4d1e2a46..579d3e4421d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: tests: build: context: . - dockerfile: Dockerfile.php-$PHP_VERSION + dockerfile: Dockerfile.php-${PHP_VERSION:-8.3} networks: - abuse depends_on: From 34177da39b2c3f17a0783082e20daa306197ba86 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 02:43:57 +0000 Subject: [PATCH 229/470] refactor adapter --- src/Abuse/Adapters/Database.php | 260 +++++++++++++++++++++++++++++++ src/Abuse/Adapters/Redis.php | 55 +------ src/Abuse/Adapters/TimeLimit.php | 241 +--------------------------- 3 files changed, 272 insertions(+), 284 deletions(-) create mode 100644 src/Abuse/Adapters/Database.php diff --git a/src/Abuse/Adapters/Database.php b/src/Abuse/Adapters/Database.php new file mode 100644 index 00000000000..534ee49904c --- /dev/null +++ b/src/Abuse/Adapters/Database.php @@ -0,0 +1,260 @@ +key = $key; + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? + $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); + $this->limit = $limit; + $this->db = $db; + } + + /** + * @throws Duplicate + * @throws Exception|\Exception + */ + public function setup(): void + { + if (! $this->db->exists($this->db->getDatabase())) { + throw new Exception('You need to create database before running timelimit setup'); + } + + $attributes = [ + new Document([ + '$id' => 'key', + 'type' => UtopiaDB::VAR_STRING, + 'size' => UtopiaDB::LENGTH_KEY, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ]), + new Document([ + '$id' => 'time', + 'type' => UtopiaDB::VAR_DATETIME, + 'size' => 0, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => ['datetime'], + ]), + new Document([ + '$id' => 'count', + 'type' => UtopiaDB::VAR_INTEGER, + 'size' => 11, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => [], + ]), + ]; + + $indexes = [ + new Document([ + '$id' => 'unique1', + 'type' => UtopiaDB::INDEX_UNIQUE, + 'attributes' => ['key', 'time'], + 'lengths' => [], + 'orders' => [], + ]), + new Document([ + '$id' => 'index2', + 'type' => UtopiaDB::INDEX_KEY, + 'attributes' => ['time'], + 'lengths' => [], + 'orders' => [], + ]), + ]; + + try { + $this->db->createCollection(Database::COLLECTION, $attributes, $indexes); + } catch (Duplicate) { + // Collection already exists + } + } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit + * + * @param string $key + * @param string $datetime + * @return int + * + * @throws \Exception + */ + protected function count(string $key, string $datetime): int + { + if (0 == $this->limit) { // No limit no point for counting + return 0; + } + + if (! \is_null($this->count)) { // Get fetched result + return $this->count; + } + + /** @var array $result */ + $result = Authorization::skip(function () use ($key, $datetime) { + return $this->db->find(Database::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), + ]); + }); + + $this->count = 0; + + if (\count($result) === 1) { // Unique Index + $count = $result[0]->getAttribute('count', 0); + if (\is_numeric($count)) { + $this->count = intval($count); + } + } + + return $this->count; + } + + /** + * @param string $key + * @param string $datetime + * @return void + * + * @throws AuthorizationException|Structure|\Exception|\Throwable + */ + protected function hit(string $key, string $datetime): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + Authorization::skip(function () use ($datetime, $key) { + $data = $this->db->findOne(Database::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), + ]); + + if ($data === false) { + $data = [ + '$permissions' => [], + 'key' => $key, + 'time' => $datetime, + 'count' => 1, + '$collection' => Database::COLLECTION, + ]; + + try { + $this->db->createDocument(Database::COLLECTION, new Document($data)); + } catch (Duplicate $e) { + // Duplicate in case of race condition + $data = $this->db->findOne(Database::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$datetime]), + ]); + + if ($data !== false && $data instanceof Document) { + $count = $data->getAttribute('count', 0); + if (\is_numeric($count)) { + $this->count = intval($count); + } + $this->db->increaseDocumentAttribute(Database::COLLECTION, $data->getId(), 'count'); + } else { + throw new \Exception('Document Not Found'); + } + } + } else { + /** @var Document $data */ + $this->db->increaseDocumentAttribute(Database::COLLECTION, $data->getId(), 'count'); + } + }); + + $this->count++; + } + + /** + * Get abuse logs + * + * Return logs with an optional offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + * + * @throws \Exception + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + /** @var array $results */ + $results = Authorization::skip(function () use ($offset, $limit) { + $queries = []; + $queries[] = Query::orderDesc(''); + + if (! \is_null($offset)) { + $queries[] = Query::offset($offset); + } + if (! \is_null($limit)) { + $queries[] = Query::limit($limit); + } + + return $this->db->find(Database::COLLECTION, $queries); + }); + + return $results; + } + + /** + * Delete logs older than $timestamp seconds + * + * @param string $datetime + * @return bool + * + * @throws AuthorizationException|\Exception + */ + public function cleanup(string $datetime): bool + { + Authorization::skip(function () use ($datetime) { + do { + $documents = $this->db->find(Database::COLLECTION, [ + Query::lessThan('time', $datetime), + ]); + + foreach ($documents as $document) { + $this->db->deleteDocument(Database::COLLECTION, $document->getId()); + } + } while (! empty($documents)); + }); + + return true; + } +} diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index ea1ab0208d3..6d482d5f538 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -2,10 +2,9 @@ namespace Utopia\Abuse\Adapters; -use Utopia\Abuse\Adapter; use Redis as Client; -class Redis extends Adapter +class Redis extends TimeLimit { public const NAMESPACE = 'abuse'; @@ -14,27 +13,12 @@ class Redis extends Adapter */ protected Client $redis; - /** - * @var int - */ - protected int $time; - - /** - * @var int - */ - protected int $limit = 0; - - /** - * @var int|null - */ - protected ?int $count = null; - public function __construct(string $key, int $limit, int $seconds, Client $redis) { $this->redis = $redis; $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? - $this->time = $time; + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); + $this->time = strval($time); $this->limit = $limit; } @@ -42,10 +26,10 @@ public function __construct(string $key, int $limit, int $seconds, Client $redis * Undocumented function * * @param string $key - * @param int $datetime + * @param string $datetime * @return integer */ - protected function count(string $key, int $datetime): int + protected function count(string $key, string $datetime): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -67,11 +51,11 @@ protected function count(string $key, int $datetime): int /** * @param string $key - * @param int $datetime + * @param string $datetime * @return void * */ - protected function hit(string $key, int $datetime): void + protected function hit(string $key, string $datetime): void { if (0 == $this->limit) { // No limit no point for counting return; @@ -88,30 +72,6 @@ protected function hit(string $key, int $datetime): void $this->count++; } - /** - * Check - * - * Checks if number of counts is bigger or smaller than current limit - * - * @return bool - */ - public function check(): bool - { - if (0 == $this->limit) { - return false; - } - - $key = $this->parseKey(); - - if ($this->limit > $this->count($key, $this->time)) { - $this->hit($key, $this->time); - - return false; - } - - return true; - } - /** * Get abuse logs * @@ -145,7 +105,6 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(string $datetime): bool { - // TODO $iterator = null; while ($iterator !== 0) { $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:*', 1000); diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index f654c98acbc..8040dc3b9ae 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -4,30 +4,9 @@ use Throwable; use Utopia\Abuse\Adapter; -use Utopia\Database\Database; -use Utopia\Database\DateTime; -use Utopia\Database\Document; -use Utopia\Database\Exception\Authorization as AuthorizationException; -use Utopia\Database\Exception\Duplicate; -use Utopia\Database\Exception\Structure; -use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; -use Utopia\Exception; -class TimeLimit extends Adapter +abstract class TimeLimit extends Adapter { - public const COLLECTION = 'abuse'; - - /** - * @var Database - */ - protected Database $db; - - /** - * @var string - */ - protected string $time; - /** * @var int */ @@ -39,83 +18,9 @@ class TimeLimit extends Adapter protected ?int $count = null; /** - * @param string $key - * @param int $seconds - * @param int $limit - * @param Database $db - */ - public function __construct(string $key, int $limit, int $seconds, Database $db) - { - $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? - $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); - $this->limit = $limit; - $this->db = $db; - } - - /** - * @throws Duplicate - * @throws Exception|\Exception + * @var string */ - public function setup(): void - { - if (! $this->db->exists($this->db->getDatabase())) { - throw new Exception('You need to create database before running timelimit setup'); - } - - $attributes = [ - new Document([ - '$id' => 'key', - 'type' => Database::VAR_STRING, - 'size' => Database::LENGTH_KEY, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'time', - 'type' => Database::VAR_DATETIME, - 'size' => 0, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => ['datetime'], - ]), - new Document([ - '$id' => 'count', - 'type' => Database::VAR_INTEGER, - 'size' => 11, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => [], - ]), - ]; - - $indexes = [ - new Document([ - '$id' => 'unique1', - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['key', 'time'], - 'lengths' => [], - 'orders' => [], - ]), - new Document([ - '$id' => 'index2', - 'type' => Database::INDEX_KEY, - 'attributes' => ['time'], - 'lengths' => [], - 'orders' => [], - ]), - ]; - - try { - $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); - } catch (Duplicate) { - // Collection already exists - } - } + protected string $time; /** * Check @@ -128,147 +33,11 @@ public function setup(): void * * @throws \Exception */ - protected function count(string $key, string $datetime): int - { - if (0 == $this->limit) { // No limit no point for counting - return 0; - } + abstract protected function count(string $key, string $datetime): int; - if (! \is_null($this->count)) { // Get fetched result - return $this->count; - } + abstract protected function hit(string $key, string $datetime): void; - /** @var array $result */ - $result = Authorization::skip(function () use ($key, $datetime) { - return $this->db->find(TimeLimit::COLLECTION, [ - Query::equal('key', [$key]), - Query::equal('time', [$datetime]), - ]); - }); - $this->count = 0; - - if (\count($result) === 1) { // Unique Index - $count = $result[0]->getAttribute('count', 0); - if (\is_numeric($count)) { - $this->count = intval($count); - } - } - - return $this->count; - } - - /** - * @param string $key - * @param string $datetime - * @return void - * - * @throws AuthorizationException|Structure|\Exception|\Throwable - */ - protected function hit(string $key, string $datetime): void - { - if (0 == $this->limit) { // No limit no point for counting - return; - } - - Authorization::skip(function () use ($datetime, $key) { - $data = $this->db->findOne(TimeLimit::COLLECTION, [ - Query::equal('key', [$key]), - Query::equal('time', [$datetime]), - ]); - - if ($data === false) { - $data = [ - '$permissions' => [], - 'key' => $key, - 'time' => $datetime, - 'count' => 1, - '$collection' => TimeLimit::COLLECTION, - ]; - - try { - $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); - } catch (Duplicate $e) { - // Duplicate in case of race condition - $data = $this->db->findOne(TimeLimit::COLLECTION, [ - Query::equal('key', [$key]), - Query::equal('time', [$datetime]), - ]); - - if ($data !== false && $data instanceof Document) { - $count = $data->getAttribute('count', 0); - if (\is_numeric($count)) { - $this->count = intval($count); - } - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); - } else { - throw new \Exception('Document Not Found'); - } - } - } else { - /** @var Document $data */ - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); - } - }); - - $this->count++; - } - - /** - * Get abuse logs - * - * Return logs with an optional offset and limit - * - * @param int|null $offset - * @param int|null $limit - * @return array - * - * @throws \Exception - */ - public function getLogs(?int $offset = null, ?int $limit = 25): array - { - /** @var array $results */ - $results = Authorization::skip(function () use ($offset, $limit) { - $queries = []; - $queries[] = Query::orderDesc(''); - - if (! \is_null($offset)) { - $queries[] = Query::offset($offset); - } - if (! \is_null($limit)) { - $queries[] = Query::limit($limit); - } - - return $this->db->find(TimeLimit::COLLECTION, $queries); - }); - - return $results; - } - - /** - * Delete logs older than $timestamp seconds - * - * @param string $datetime - * @return bool - * - * @throws AuthorizationException|\Exception - */ - public function cleanup(string $datetime): bool - { - Authorization::skip(function () use ($datetime) { - do { - $documents = $this->db->find(TimeLimit::COLLECTION, [ - Query::lessThan('time', $datetime), - ]); - - foreach ($documents as $document) { - $this->db->deleteDocument(TimeLimit::COLLECTION, $document->getId()); - } - } while (! empty($documents)); - }); - - return true; - } /** * Check From 5cc6a702168f21f2b43548bbdaf864d1136d5024 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 02:48:48 +0000 Subject: [PATCH 230/470] fix --- tests/Abuse/TimeLimitTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Abuse/TimeLimitTest.php b/tests/Abuse/TimeLimitTest.php index 495dae91376..b641b0814ec 100755 --- a/tests/Abuse/TimeLimitTest.php +++ b/tests/Abuse/TimeLimitTest.php @@ -5,7 +5,7 @@ use PDO; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Abuse\Adapters\Database as AdaptersDatabase; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; @@ -37,7 +37,7 @@ public function setUp(): void $db->setNamespace('namespace'); $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + $adapter = new AdaptersDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -49,7 +49,7 @@ public function setUp(): void public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new TimeLimit($key, $limit, $seconds, $this->db); + return new AdaptersDatabase($key, $limit, $seconds, $this->db); } public function getCleanupDateTime(): string From b015f9c334a680e8daee866c8ceae3315b66d3b9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 03:03:54 +0000 Subject: [PATCH 231/470] fix dockerfile --- .github/workflows/tests.yml | 2 +- Dockerfile.php-8.2 | 18 ++++++++++++++++++ Dockerfile.php-8.3 | 18 ++++++++++++++++++ phpunit.xml | 2 +- tests/Abuse/TimeLimitTest.php | 2 +- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8fa5b551af9..880da7f6936 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,4 +25,4 @@ jobs: sleep 10 - name: Run Tests - run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml tests \ No newline at end of file + run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml \ No newline at end of file diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index 74b2276598e..aaedc179bae 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -14,6 +14,24 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql + +RUN \ + apk update \ + && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ + && rm -rf /var/cache/apk/* + +RUN \ + # Redis Extension + git clone https://github.com/phpredis/phpredis.git && \ + cd phpredis && \ + git checkout $PHP_REDIS_VERSION && \ + phpize && \ + ./configure && \ + make && make install && \ + cd .. + +RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini + WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index 8e8fc44e914..0483119daa0 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -14,6 +14,24 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql + +RUN \ + apk update \ + && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ + && rm -rf /var/cache/apk/* + +RUN \ + # Redis Extension + git clone https://github.com/phpredis/phpredis.git && \ + cd phpredis && \ + git checkout $PHP_REDIS_VERSION && \ + phpize && \ + ./configure && \ + make && make install && \ + cd .. + +RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini + WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/phpunit.xml b/phpunit.xml index 10d408caa08..ec39a7edeaf 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > diff --git a/tests/Abuse/TimeLimitTest.php b/tests/Abuse/TimeLimitTest.php index b641b0814ec..5927d0f72fc 100755 --- a/tests/Abuse/TimeLimitTest.php +++ b/tests/Abuse/TimeLimitTest.php @@ -14,7 +14,7 @@ use Utopia\Database\DateTime; use Utopia\Exception; -class TimeLimitTest extends Base +class DatabaseTest extends Base { protected Database $db; From d9b99572713c17d51f41ed0f450f692c6ec833bd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 03:09:16 +0000 Subject: [PATCH 232/470] add phpbench --- composer.json | 3 +- composer.lock | 1650 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 1587 insertions(+), 66 deletions(-) diff --git a/composer.json b/composer.json index d5513a3b9bd..09ed8c97d26 100755 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "require-dev": { "phpunit/phpunit": "^9.4", "phpstan/phpstan": "^1.9", - "laravel/pint": "1.5.*" + "laravel/pint": "1.5.*", + "phpbench/phpbench": "^1.2" } } diff --git a/composer.lock b/composer.lock index 4179b93ab48..cb071d15a0a 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": "f78eeda391d225f378154fe10e288486", + "content-hash": "f6771982733371b9bc1730cd526fa6cd", "packages": [ { "name": "jean85/pretty-package-versions", @@ -467,6 +467,82 @@ } ], "packages-dev": [ + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.1" + }, + "time": "2023-02-02T22:02:53+00:00" + }, { "name": "doctrine/instantiator", "version": "2.0.0", @@ -537,6 +613,83 @@ ], "time": "2022-12-30T00:23:10+00:00" }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, { "name": "laravel/pint", "version": "v1.5.0", @@ -838,6 +991,206 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpbench/container", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33", + "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.2" + }, + "time": "2023-10-30T13:38:26+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "786a96db538d0def931f5b19225233ec42ec7a72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/786a96db538d0def931f5b19225233ec42ec7a72", + "reference": "786a96db538d0def931f5b19225233ec42ec7a72", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.3||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.3" + }, + "time": "2023-03-06T23:46:57+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.15", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/f7000319695cfad04a57fc64bf7ef7abdf4c437c", + "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^8.1", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.3", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "webmozart/glob": "^4.6" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^1.0", + "phpspec/prophecy": "dev-master", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.0", + "rector/rector": "^0.18.10", + "symfony/error-handler": "^5.2 || ^6.0 || ^7.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "keywords": [ + "benchmarking", + "optimization", + "performance", + "profiling", + "testing" + ], + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.15" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2023-11-29T12:21:11+00:00" + }, { "name": "phpstan/phpstan", "version": "1.10.59", @@ -1323,90 +1676,242 @@ "time": "2024-02-23T13:14:51+00:00" }, { - "name": "sebastian/cli-parser", - "version": "1.0.2", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "sebastian/code-unit", - "version": "1.0.8", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2287,54 +2792,1068 @@ "time": "2020-09-28T06:39:44+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.3", + "name": "seld/jsonlint", + "version": "1.10.2", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, + "bin": [ + "bin/jsonlint" + ], "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-02-07T12:57:50+00:00" + }, + { + "name": "symfony/console", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5", + "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/symfony/console/tree/v7.0.6" }, "funding": [ { - "url": "https://github.com/theseer", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2024-04-01T11:04:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d", + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-21T19:37:36+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-10-31T17:59:56+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", + "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-08T10:20:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/process", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-22T20:27:20+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e", + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-19T21:51:00+00:00" + }, + { + "name": "symfony/string", + "version": "v7.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-02-01T13:17:36+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "webmozart/glob", + "version": "4.7.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/glob.git", + "reference": "8a2842112d6916e61e0e15e316465b611f3abc17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17", + "reference": "8a2842112d6916e61e0e15e316465b611f3abc17", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/filesystem": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Glob\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A PHP implementation of Ant's glob.", + "support": { + "issues": "https://github.com/webmozarts/glob/issues", + "source": "https://github.com/webmozarts/glob/tree/4.7.0" + }, + "time": "2024-03-07T20:33:40+00:00" } ], "aliases": [], @@ -2345,7 +3864,8 @@ "platform": { "php": ">=8.0", "ext-pdo": "*", - "ext-curl": "*" + "ext-curl": "*", + "ext-redis": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" From 6cd9a15e948e1c2be62362ac59ed955d9d3a3e26 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 03:55:08 +0000 Subject: [PATCH 233/470] basic benchmark --- Dockerfile.php-8.1 | 1 + Dockerfile.php-8.2 | 1 + Dockerfile.php-8.3 | 1 + composer.json | 3 ++- phpbench.json | 6 ++++++ tests/RedisBench.php | 39 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 phpbench.json create mode 100644 tests/RedisBench.php diff --git a/Dockerfile.php-8.1 b/Dockerfile.php-8.1 index 0eab2e91107..02731c9a5ea 100644 --- a/Dockerfile.php-8.1 +++ b/Dockerfile.php-8.1 @@ -39,5 +39,6 @@ COPY --from=step0 /src/vendor /code/vendor COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml +COPY ./phpbench.json /code/phpbench.json CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index aaedc179bae..e7f8b9de04e 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -40,5 +40,6 @@ COPY --from=step0 /src/vendor /code/vendor COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml +COPY ./phpbench.json /code/phpbench.json CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index 0483119daa0..2225efa5708 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -40,5 +40,6 @@ COPY --from=step0 /src/vendor /code/vendor COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml +COPY ./phpbench.json /code/phpbench.json CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file diff --git a/composer.json b/composer.json index 09ed8c97d26..44c0da44362 100755 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "scripts": { "check": "./vendor/bin/phpstan analyse --level max src tests", "lint": "./vendor/bin/pint --test", - "format": "./vendor/bin/pint" + "format": "./vendor/bin/pint", + "bench": "vendor/bin/phpbench run --report=benchmark" }, "require": { "php": ">=8.0", diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000000..adc40d12944 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,6 @@ +{ + "$schema":"vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests", + "runner.file_pattern": "*Bench.php" +} \ No newline at end of file diff --git a/tests/RedisBench.php b/tests/RedisBench.php new file mode 100644 index 00000000000..da5c13a47f2 --- /dev/null +++ b/tests/RedisBench.php @@ -0,0 +1,39 @@ +redis = new Client(); + $this->redis->connect('redis', 6379); + $this->adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $this->abuse = new Abuse($this->adapter); + } + + #[BeforeMethods('setUp')] + #[Iterations(50000)] + public function benchTimelimit(): void + { + $ip = ''; + for( $i = 0; $i < 4; $i++ ) { + $sub = rand(0, 255); + $ip .= $sub . '.'; + }; + $ip = ltrim($ip,'.'); + $this->adapter->setParam('{{ip}}', $ip ); + $this->abuse->check(); + } +} \ No newline at end of file From 71c93e1f4dff47bdd9c0c2af3d3b89e30d9e35bf Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 03:55:36 +0000 Subject: [PATCH 234/470] fix format --- tests/RedisBench.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/RedisBench.php b/tests/RedisBench.php index da5c13a47f2..a91e2ee556d 100644 --- a/tests/RedisBench.php +++ b/tests/RedisBench.php @@ -6,7 +6,8 @@ use PhpBench\Attributes\BeforeMethods; use PhpBench\Attributes\Iterations; -final class RedisBench { +final class RedisBench +{ protected Client $redis; protected Abuse $abuse; protected Redis $adapter; @@ -28,12 +29,12 @@ public function setUp(): void public function benchTimelimit(): void { $ip = ''; - for( $i = 0; $i < 4; $i++ ) { + for ($i = 0; $i < 4; $i++) { $sub = rand(0, 255); $ip .= $sub . '.'; }; - $ip = ltrim($ip,'.'); - $this->adapter->setParam('{{ip}}', $ip ); + $ip = ltrim($ip, '.'); + $this->adapter->setParam('{{ip}}', $ip); $this->abuse->check(); } -} \ No newline at end of file +} From f60b259dea224dbda725ecffc240d4cdb861469b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 04:58:39 +0000 Subject: [PATCH 235/470] benchmark --- tests/Abuse/Bench/Base.php | 32 +++++++++++++ tests/Abuse/Bench/DatabaseBench.php | 46 +++++++++++++++++++ tests/Abuse/Bench/RedisBench.php | 24 ++++++++++ .../{TimeLimitTest.php => DatabaseTest.php} | 0 tests/RedisBench.php | 40 ---------------- 5 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 tests/Abuse/Bench/Base.php create mode 100644 tests/Abuse/Bench/DatabaseBench.php create mode 100644 tests/Abuse/Bench/RedisBench.php rename tests/Abuse/{TimeLimitTest.php => DatabaseTest.php} (100%) delete mode 100644 tests/RedisBench.php diff --git a/tests/Abuse/Bench/Base.php b/tests/Abuse/Bench/Base.php new file mode 100644 index 00000000000..e78d7af89b8 --- /dev/null +++ b/tests/Abuse/Bench/Base.php @@ -0,0 +1,32 @@ +adapter->setParam('{{ip}}', $ip); + $this->abuse->check(); + } +} diff --git a/tests/Abuse/Bench/DatabaseBench.php b/tests/Abuse/Bench/DatabaseBench.php new file mode 100644 index 00000000000..fcba9517fd4 --- /dev/null +++ b/tests/Abuse/Bench/DatabaseBench.php @@ -0,0 +1,46 @@ +setDatabase('utopiaTests'); + $db->setNamespace('namespace'); + $this->db = $db; + + $adapter = new AdaptersDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + if (! $db->exists('utopiaTests')) { + $db->create(); + $adapter->setup(); + } + $this->adapter = $adapter; + $this->abuse = new Abuse($this->adapter); + } +} diff --git a/tests/Abuse/Bench/RedisBench.php b/tests/Abuse/Bench/RedisBench.php new file mode 100644 index 00000000000..d30b63c4d31 --- /dev/null +++ b/tests/Abuse/Bench/RedisBench.php @@ -0,0 +1,24 @@ +redis = new Client(); + $this->redis->connect('redis', 6379); + $this->adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $this->abuse = new Abuse($this->adapter); + } +} diff --git a/tests/Abuse/TimeLimitTest.php b/tests/Abuse/DatabaseTest.php similarity index 100% rename from tests/Abuse/TimeLimitTest.php rename to tests/Abuse/DatabaseTest.php diff --git a/tests/RedisBench.php b/tests/RedisBench.php deleted file mode 100644 index a91e2ee556d..00000000000 --- a/tests/RedisBench.php +++ /dev/null @@ -1,40 +0,0 @@ -redis = new Client(); - $this->redis->connect('redis', 6379); - $this->adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); - $this->abuse = new Abuse($this->adapter); - } - - #[BeforeMethods('setUp')] - #[Iterations(50000)] - public function benchTimelimit(): void - { - $ip = ''; - for ($i = 0; $i < 4; $i++) { - $sub = rand(0, 255); - $ip .= $sub . '.'; - }; - $ip = ltrim($ip, '.'); - $this->adapter->setParam('{{ip}}', $ip); - $this->abuse->check(); - } -} From 4eb7fc5846a8bb0d4b376ef9f1b400cd79df22c4 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 10 Apr 2024 04:58:56 +0000 Subject: [PATCH 236/470] aggregate --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 44c0da44362..57e21c1a851 100755 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "check": "./vendor/bin/phpstan analyse --level max src tests", "lint": "./vendor/bin/pint --test", "format": "./vendor/bin/pint", - "bench": "vendor/bin/phpbench run --report=benchmark" + "bench": "vendor/bin/phpbench run --report=aggregate" }, "require": { "php": ">=8.0", From 36113abc0d4f98556b09cfba2af479b806672206 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 16:38:33 +0300 Subject: [PATCH 237/470] composer.lock --- composer.lock | 53 +++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/composer.lock b/composer.lock index 9c8ec72f89a..c0917024dc3 100644 --- a/composer.lock +++ b/composer.lock @@ -216,16 +216,16 @@ }, { "name": "utopia-php/cache", - "version": "0.9.0", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e" + "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", - "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/552b4c554bb14d0c529631ce304cdf4a2b9d06a6", + "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6", "shasum": "" }, "require": { @@ -260,9 +260,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.9.0" + "source": "https://github.com/utopia-php/cache/tree/0.9.1" }, - "time": "2024-01-07T18:11:23+00:00" + "time": "2024-03-19T17:07:20+00:00" }, { "name": "utopia-php/database", @@ -843,16 +843,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.10.67", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -895,13 +895,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-04-16T07:22:02+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1224,16 +1220,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -1307,7 +1303,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -1323,7 +1319,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "sebastian/cli-parser", @@ -2127,16 +2123,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -2148,7 +2144,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2169,8 +2165,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -2178,7 +2173,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", From 46f3177ce2f91a8c8468e42e18e0d3c16febb79d Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 18:55:23 +0300 Subject: [PATCH 238/470] composer.lock --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index c0917024dc3..54a78c576e4 100644 --- a/composer.lock +++ b/composer.lock @@ -270,12 +270,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "bdd9140e40c77faadb0cf2f5050b466c34eef673" + "reference": "0645d8550cbcdb921d072b05f05b0f979442d94f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/bdd9140e40c77faadb0cf2f5050b466c34eef673", - "reference": "bdd9140e40c77faadb0cf2f5050b466c34eef673", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0645d8550cbcdb921d072b05f05b0f979442d94f", + "reference": "0645d8550cbcdb921d072b05f05b0f979442d94f", "shasum": "" }, "require": { @@ -319,7 +319,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" }, - "time": "2024-03-07T16:55:44+00:00" + "time": "2024-04-17T14:28:28+00:00" }, { "name": "utopia-php/fetch", From 88f74c0726938769ea1ff82c77bd1213816c7c04 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 19:32:58 +0300 Subject: [PATCH 239/470] auth test --- src/Abuse/Adapters/TimeLimit.php | 28 ++++++++++++++++++---------- tests/Abuse/AbuseTest.php | 6 ++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 4ce97ca9ab3..36d647806fc 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -7,12 +7,12 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Exception; class TimeLimit implements Adapter { @@ -51,10 +51,11 @@ class TimeLimit implements Adapter protected Authorization $auth; /** - * @param string $key - * @param int $seconds - * @param int $limit - * @param Database $db + * @param string $key + * @param int $limit + * @param int $seconds + * @param Database $db + * @param Authorization $auth */ public function __construct(string $key, int $limit, int $seconds, Database $db, Authorization $auth) { @@ -68,12 +69,12 @@ public function __construct(string $key, int $limit, int $seconds, Database $db, /** * @throws Duplicate - * @throws Exception|\Exception + * @throws Exception */ public function setup(): void { if (! $this->db->exists($this->db->getDatabase())) { - throw new \Exception('You need to create database before running timelimit setup'); + throw new Exception('You need to create database before running timelimit setup'); } $attributes = [ @@ -194,6 +195,14 @@ protected function count(string $key, string $datetime): int } /** @var array $result */ + + /** + * todo: why we are using + * $this->auth->skip + * and not ? + * $this->db->getAuthorization()->skip + */ + $result = $this->auth->skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), @@ -250,7 +259,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data !== false && $data instanceof Document) { + if ($data instanceof Document) { $count = $data->getAttribute('count', 0); if (\is_numeric($count)) { $this->count = intval($count); @@ -261,7 +270,6 @@ protected function hit(string $key, string $datetime): void } } } else { - /** @var Document $data */ $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } }); @@ -332,7 +340,7 @@ public function cleanup(string $datetime): bool * * @return bool * - * @throws \Exception|Throwable + * @throws Throwable */ public function check(): bool { diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 755b6e6d677..7f02ab90e9e 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -12,7 +12,8 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Exception; +use Utopia\Database\Exception; +use Utopia\Database\Validator\Authorization; class AbuseTest extends TestCase { @@ -39,9 +40,10 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); + $db->setAuthorization(new Authorization()); $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, new Authorization()); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); From 7cd611bbf82115dd53805b36424a8c0b96c1f13d Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 19:43:34 +0300 Subject: [PATCH 240/470] Question --- src/Abuse/Adapters/TimeLimit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 36d647806fc..63ef6294300 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -201,6 +201,7 @@ protected function count(string $key, string $datetime): int * $this->auth->skip * and not ? * $this->db->getAuthorization()->skip + * are they different objects? */ $result = $this->auth->skip(function () use ($key, $datetime) { From a58454e367a827285415092e7e393559025f3faf Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 20:05:33 +0300 Subject: [PATCH 241/470] Add objects --- tests/Abuse/AbuseTest.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 7f02ab90e9e..0f5c384cb2f 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -22,6 +22,7 @@ class AbuseTest extends TestCase protected Abuse $abuseIp; protected Database $db; + protected Authorization $authorization; /** * @throws Exception @@ -40,10 +41,13 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); - $db->setAuthorization(new Authorization()); + + $this->authorization = new Authorization(); + $db->setAuthorization($this->authorization); $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, new Authorization()); + // todo do I need to pass a new $auth ? or can I use the one from DB? + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, $this->authorization); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -63,7 +67,7 @@ public function testImitate2Requests(): void $key = '{{ip}}'; $value = '0.0.0.10'; - $adapter = new TimeLimit($key, 1, 1, $this->db); + $adapter = new TimeLimit($key, 1, 1, $this->db, new Authorization()); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); @@ -71,7 +75,7 @@ public function testImitate2Requests(): void sleep(1); - $adapter = new TimeLimit($key, 1, 1, $this->db); + $adapter = new TimeLimit($key, 1, 1, $this->db, new Authorization()); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); From db3dbb5603c62e1e8f87480e77f9b66553bed43a Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 17 Apr 2024 20:08:09 +0300 Subject: [PATCH 242/470] Question --- tests/Abuse/AbuseTest.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 0f5c384cb2f..31f149dce3d 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -22,7 +22,6 @@ class AbuseTest extends TestCase protected Abuse $abuseIp; protected Database $db; - protected Authorization $authorization; /** * @throws Exception @@ -42,12 +41,12 @@ public function setUp(): void $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); - $this->authorization = new Authorization(); - $db->setAuthorization($this->authorization); + $auth = new Authorization(); + $db->setAuthorization($auth); $this->db = $db; - // todo do I need to pass a new $auth ? or can I use the one from DB? - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, $this->authorization); + // todo: Should I create a new Authorization() or can I use $auth from the db? + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, $auth); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); From 70a3a0a9aa6dfea5562be88d0f2e16a92e4e95ac Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Apr 2024 16:07:17 +0300 Subject: [PATCH 243/470] Use db auth --- composer.lock | 8 ++++---- src/Abuse/Adapters/TimeLimit.php | 22 +++++----------------- tests/Abuse/AbuseTest.php | 4 ++-- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/composer.lock b/composer.lock index 54a78c576e4..db03c559755 100644 --- a/composer.lock +++ b/composer.lock @@ -270,12 +270,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0645d8550cbcdb921d072b05f05b0f979442d94f" + "reference": "6b8264a4e269d679182fc376179281b7108a4242" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0645d8550cbcdb921d072b05f05b0f979442d94f", - "reference": "0645d8550cbcdb921d072b05f05b0f979442d94f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6b8264a4e269d679182fc376179281b7108a4242", + "reference": "6b8264a4e269d679182fc376179281b7108a4242", "shasum": "" }, "require": { @@ -319,7 +319,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" }, - "time": "2024-04-17T14:28:28+00:00" + "time": "2024-04-18T12:50:22+00:00" }, { "name": "utopia-php/fetch", diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 63ef6294300..b01d4b7853c 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -48,18 +48,14 @@ class TimeLimit implements Adapter */ protected array $params = []; - protected Authorization $auth; - /** * @param string $key * @param int $limit * @param int $seconds * @param Database $db - * @param Authorization $auth */ - public function __construct(string $key, int $limit, int $seconds, Database $db, Authorization $auth) + public function __construct(string $key, int $limit, int $seconds, Database $db) { - $this->auth = $auth; $this->key = $key; $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); @@ -196,15 +192,7 @@ protected function count(string $key, string $datetime): int /** @var array $result */ - /** - * todo: why we are using - * $this->auth->skip - * and not ? - * $this->db->getAuthorization()->skip - * are they different objects? - */ - - $result = $this->auth->skip(function () use ($key, $datetime) { + $result = $this->db->getAuthorization()->skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -236,7 +224,7 @@ protected function hit(string $key, string $datetime): void return; } - $this->auth->skip(function () use ($datetime, $key) { + $this->db->getAuthorization()->skip(function () use ($datetime, $key) { $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -292,7 +280,7 @@ protected function hit(string $key, string $datetime): void public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ - $results = $this->auth->skip(function () use ($offset, $limit) { + $results = $this->db->getAuthorization()->skip(function () use ($offset, $limit) { $queries = []; $queries[] = Query::orderDesc(''); @@ -319,7 +307,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(string $datetime): bool { - $this->auth->skip(function () use ($datetime) { + $this->db->getAuthorization()->skip(function () use ($datetime) { do { $documents = $this->db->find(TimeLimit::COLLECTION, [ Query::lessThan('time', $datetime), diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 31f149dce3d..0497311e5ee 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -66,7 +66,7 @@ public function testImitate2Requests(): void $key = '{{ip}}'; $value = '0.0.0.10'; - $adapter = new TimeLimit($key, 1, 1, $this->db, new Authorization()); + $adapter = new TimeLimit($key, 1, 1, $this->db); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); @@ -74,7 +74,7 @@ public function testImitate2Requests(): void sleep(1); - $adapter = new TimeLimit($key, 1, 1, $this->db, new Authorization()); + $adapter = new TimeLimit($key, 1, 1, $this->db); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); From 56e67231ed156a27fcd605eb2a4a890ee2342fa1 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Apr 2024 17:38:55 +0300 Subject: [PATCH 244/470] setAuthorization --- tests/Abuse/AbuseTest.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 0497311e5ee..2247a15ca07 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -40,13 +40,10 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); - - $auth = new Authorization(); - $db->setAuthorization($auth); + $db->setAuthorization(new Authorization()); $this->db = $db; - // todo: Should I create a new Authorization() or can I use $auth from the db? - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db, $auth); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); From fffd9c0a4d1f0d0b8f5730a5523c91c75977a0dc Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Apr 2024 17:55:04 +0300 Subject: [PATCH 245/470] remove setAuthorization --- tests/Abuse/AbuseTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 2247a15ca07..26fbd3d47a2 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -40,7 +40,6 @@ public function setUp(): void $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); - $db->setAuthorization(new Authorization()); $this->db = $db; $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); From a2292d71da901ea13129d56f89876626ba92adf0 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 18 Apr 2024 20:04:17 +0300 Subject: [PATCH 246/470] Formatting --- src/Abuse/Adapters/TimeLimit.php | 1 - tests/Abuse/AbuseTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index b01d4b7853c..1308f09f625 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -12,7 +12,6 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; class TimeLimit implements Adapter { diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/AbuseTest.php index 26fbd3d47a2..5f6048de403 100755 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/AbuseTest.php @@ -13,7 +13,6 @@ use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Exception; -use Utopia\Database\Validator\Authorization; class AbuseTest extends TestCase { From 1d40508542d0beda1bbc9a5b7a9aff233fd3d85f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 13 May 2024 07:04:57 +0000 Subject: [PATCH 247/470] benchmark workflow --- .github/workflows/bench.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/bench.yml diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 00000000000..c0c51368f72 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,16 @@ +name: "Benchmarks" + +on: [pull_request] +jobs: + lint: + name: Benchmarks + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Run Linter + run: | + docker run --rm -v $PWD:/app composer sh -c \ + "composer install --profile --ignore-platform-reqs && git config --global --add safe.directory /app && composer bench -- --progress=plain" \ No newline at end of file From 06d651840f40b96d7e234705cd4bd7bc382d5b64 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 13 May 2024 07:05:52 +0000 Subject: [PATCH 248/470] fix codeql errors --- tests/Abuse/Bench/RedisBench.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Abuse/Bench/RedisBench.php b/tests/Abuse/Bench/RedisBench.php index d30b63c4d31..b3ad310d08a 100644 --- a/tests/Abuse/Bench/RedisBench.php +++ b/tests/Abuse/Bench/RedisBench.php @@ -11,7 +11,6 @@ final class RedisBench extends Base protected Client $redis; /** - * @throws Exception * @throws \Exception */ public function setUp(): void From a9495d20d9532f6cc5a86e57d6f83cd9ec4fa79e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 13 May 2024 07:10:10 +0000 Subject: [PATCH 249/470] benchmark --- .github/workflows/bench.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index c0c51368f72..153ed8a9b5a 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -9,8 +9,13 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + + - name: Build + run: | + docker compose build + docker compose up -d + sleep 10 - - name: Run Linter + - name: Run benchmark run: | - docker run --rm -v $PWD:/app composer sh -c \ - "composer install --profile --ignore-platform-reqs && git config --global --add safe.directory /app && composer bench -- --progress=plain" \ No newline at end of file + docker compose exec tests vendor/bin/phpbench run --report=aggregate --progress=plain \ No newline at end of file From ea1392dae7741010287c1d66d3631eec2878eafc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 19 May 2024 01:02:23 +0000 Subject: [PATCH 250/470] change separator --- src/Abuse/Adapters/Redis.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index 6d482d5f538..e71a93706b8 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -39,7 +39,7 @@ protected function count(string $key, string $datetime): int return $this->count; } - $count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); if (!$count) { $this->count = 0; } else { @@ -61,14 +61,14 @@ protected function hit(string $key, string $datetime): void return; } - $count = $this->redis->get(self::NAMESPACE . ':'. $key .':'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); if (!$count) { $this->count = 0; } else { $this->count = (int) $count; } - $this->redis->incr(self::NAMESPACE . ':'. $key .':'. $datetime); + $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $datetime); $this->count++; } @@ -85,7 +85,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset $cursor = null; - $keys = $this->redis->scan($cursor, self::NAMESPACE . ':*', $limit); + $keys = $this->redis->scan($cursor, self::NAMESPACE . '__*', $limit); if (!$keys) { return []; } @@ -107,7 +107,7 @@ public function cleanup(string $datetime): bool { $iterator = null; while ($iterator !== 0) { - $keys = $this->redis->scan($iterator, self::NAMESPACE . ':*:*', 1000); + $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 1000); $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); $this->redis->del($keys); } @@ -125,7 +125,7 @@ protected function filterKeys(array $keys, int $timestamp): array { $filteredKeys = []; foreach ($keys as $key) { - $parts = explode(':', $key); + $parts = explode('__', $key); $keyTimestamp = (int)end($parts); // Assuming the last part is always the timestamp if ($keyTimestamp < $timestamp) { $filteredKeys[] = $key; From bb957a60c4c238e963cc256587fac4b240e47f48 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 21 May 2024 10:53:03 +0545 Subject: [PATCH 251/470] Update Dockerfile.php-8.1 --- Dockerfile.php-8.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.php-8.1 b/Dockerfile.php-8.1 index df1a3073d07..1ae98fbd459 100644 --- a/Dockerfile.php-8.1 +++ b/Dockerfile.php-8.1 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM php:8.1-cli-alpine as final +FROM appwrite/utopia-base:php-8.1-0.1.0 as final LABEL maintainer="team@appwrite.io" @@ -23,4 +23,4 @@ COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml -CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file +CMD [ "tail", "-f", "/dev/null" ] From 35ee385fc9e720c4cdf3f8c539ec5b7caf4d415e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 21 May 2024 10:53:27 +0545 Subject: [PATCH 252/470] Update Dockerfile.php-8.2 --- Dockerfile.php-8.2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index 74b2276598e..0cf128deae6 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM php:8.2-cli-alpine as final +FROM appwrite/utopia-base:php-8.2-0.1.0 as final LABEL maintainer="team@appwrite.io" @@ -23,4 +23,4 @@ COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml -CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file +CMD [ "tail", "-f", "/dev/null" ] From 684c3cd6583c00a496e27b023fcf945ecc68ba2f Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 21 May 2024 10:53:52 +0545 Subject: [PATCH 253/470] Update Dockerfile.php-8.3 --- Dockerfile.php-8.3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index 8e8fc44e914..23bab2ddbca 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM php:8.3-cli-alpine as final +FROM appwrite/utopia-base:php-8.3-0.1.0 as final LABEL maintainer="team@appwrite.io" @@ -23,4 +23,4 @@ COPY ./tests /code/tests COPY ./src /code/src COPY ./phpunit.xml /code/phpunit.xml -CMD [ "tail", "-f", "/dev/null" ] \ No newline at end of file +CMD [ "tail", "-f", "/dev/null" ] From 5251153dde9cc8b47b8c95b70aa077e4b63ce73f Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 5 Jun 2024 21:00:16 +0300 Subject: [PATCH 254/470] version change --- composer.lock | 137 +++++++++++++++++--------------------------------- 1 file changed, 46 insertions(+), 91 deletions(-) diff --git a/composer.lock b/composer.lock index 4179b93ab48..e38e79de823 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "jean85/pretty-package-versions", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", "shasum": "" }, "require": { @@ -25,9 +25,9 @@ "php": "^7.1|^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^0.12.66", + "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^7.5|^8.5|^9.4", "vimeo/psalm": "^4.3" }, @@ -61,9 +61,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" }, - "time": "2021-10-08T21:21:46+00:00" + "time": "2024-03-08T09:58:59+00:00" }, { "name": "mongodb/mongodb", @@ -216,16 +216,16 @@ }, { "name": "utopia-php/cache", - "version": "0.9.0", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e" + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", - "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/313bcdfbb166f75c2c205a59d1467cead63a9626", + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626", "shasum": "" }, "require": { @@ -260,30 +260,29 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.9.0" + "source": "https://github.com/utopia-php/cache/tree/0.10.0" }, - "time": "2024-01-07T18:11:23+00:00" + "time": "2024-06-05T16:40:43+00:00" }, { "name": "utopia-php/database", - "version": "0.49.1", + "version": "0.49.12", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03" + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4199fe8f00f4e181c7782c4a6862845d591c1f03", - "reference": "4199fe8f00f4e181c7782c4a6862845d591c1f03", + "url": "https://api.github.com/repos/utopia-php/database/zipball/45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/cache": "0.9.*", - "utopia-php/fetch": "0.1.*", + "utopia-php/cache": "0.10.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, @@ -317,61 +316,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.1" + "source": "https://github.com/utopia-php/database/tree/0.49.12" }, - "time": "2024-03-06T11:35:53+00:00" - }, - { - "name": "utopia-php/fetch", - "version": "0.1.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/fetch.git", - "reference": "2fa214b9262acd1a3583515a364da4f35929d5c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/fetch/zipball/2fa214b9262acd1a3583515a364da4f35929d5c5", - "reference": "2fa214b9262acd1a3583515a364da4f35929d5c5", - "shasum": "" - }, - "require": { - "php": ">=8.0" - }, - "require-dev": { - "laravel/pint": "^1.5.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Fetch\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple library that provides an interface for making HTTP Requests.", - "support": { - "issues": "https://github.com/utopia-php/fetch/issues", - "source": "https://github.com/utopia-php/fetch/tree/0.1.0" - }, - "time": "2023-10-10T11:58:32+00:00" + "time": "2024-06-05T16:52:59+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.2", + "version": "0.33.6", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3" + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", - "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", + "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", "shasum": "" }, "require": { @@ -401,9 +361,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.2" + "source": "https://github.com/utopia-php/http/tree/0.33.6" }, - "time": "2024-01-31T10:35:59+00:00" + "time": "2024-03-21T18:10:57+00:00" }, { "name": "utopia-php/mongo", @@ -840,16 +800,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.59", + "version": "1.11.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e607609388d3a6d418a50a49f7940e8086798281" + "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281", - "reference": "e607609388d3a6d418a50a49f7940e8086798281", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5", + "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5", "shasum": "" }, "require": { @@ -892,13 +852,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-02-20T13:59:13+00:00" + "time": "2024-05-31T13:53:37+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1221,16 +1177,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -1304,7 +1260,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -1320,7 +1276,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "sebastian/cli-parser", @@ -2124,16 +2080,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -2145,7 +2101,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2166,8 +2122,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -2175,7 +2130,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -2348,5 +2303,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } From 9f43c3ce24fe71bf6a807caeb917cf73be15f37e Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 21 Jun 2024 18:50:22 +0000 Subject: [PATCH 255/470] chore: bump utopia-php/database version --- composer.json | 2 +- composer.lock | 65 ++++++++++++++++++++++++++------------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 29d77f51d5e..16b4222ef0d 100755 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", - "utopia-php/database": "0.49.*" + "utopia-php/database": "0.50.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index e38e79de823..133c2379e58 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": "f78eeda391d225f378154fe10e288486", + "content-hash": "c01bd34998a95089e8e5f980d54fe747", "packages": [ { "name": "jean85/pretty-package-versions", @@ -136,16 +136,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -196,7 +196,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -212,20 +212,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "utopia-php/cache", - "version": "0.10.0", + "version": "0.10.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626" + "reference": "87ee4fc91e50d4ddfef650aa999ea12be3a99583" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/313bcdfbb166f75c2c205a59d1467cead63a9626", - "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/87ee4fc91e50d4ddfef650aa999ea12be3a99583", + "reference": "87ee4fc91e50d4ddfef650aa999ea12be3a99583", "shasum": "" }, "require": { @@ -260,22 +260,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.10.0" + "source": "https://github.com/utopia-php/cache/tree/0.10.1" }, - "time": "2024-06-05T16:40:43+00:00" + "time": "2024-06-18T13:20:25+00:00" }, { "name": "utopia-php/database", - "version": "0.49.12", + "version": "0.50.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63" + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", - "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.12" + "source": "https://github.com/utopia-php/database/tree/0.50.0" }, - "time": "2024-06-05T16:52:59+00:00" + "time": "2024-06-21T03:21:42+00:00" }, { "name": "utopia-php/framework", @@ -565,16 +565,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -582,11 +582,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -612,7 +613,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -620,7 +621,7 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", @@ -800,16 +801,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.3", + "version": "1.11.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5" + "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5", - "reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", + "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", "shasum": "" }, "require": { @@ -854,7 +855,7 @@ "type": "github" } ], - "time": "2024-05-31T13:53:37+00:00" + "time": "2024-06-17T15:10:54+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2303,5 +2304,5 @@ "ext-curl": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } From 0116fbaad68228ae76a62fc3bab2c323808eec53 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 30 Jun 2024 14:36:38 +0000 Subject: [PATCH 256/470] fix codeql --- src/Abuse/Adapters/Redis.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis.php index e71a93706b8..d9b0892dd50 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis.php @@ -39,11 +39,12 @@ protected function count(string $key, string $datetime): int return $this->count; } + /** @var string $count */ $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); if (!$count) { $this->count = 0; } else { - $this->count = (int) $count; + $this->count = intval($count); } return $this->count; @@ -61,11 +62,12 @@ protected function hit(string $key, string $datetime): void return; } + /** @var string $count */ $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); if (!$count) { $this->count = 0; } else { - $this->count = (int) $count; + $this->count = intval($count); } $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $datetime); From 21a14d4575b06cf3850d2fc7b5c5a765eb155bb7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 30 Jun 2024 14:43:59 +0000 Subject: [PATCH 257/470] fix dockerfile --- Dockerfile.php-8.1 | 17 ----------------- Dockerfile.php-8.2 | 18 ------------------ Dockerfile.php-8.3 | 18 ------------------ docker-compose.yml | 1 + 4 files changed, 1 insertion(+), 53 deletions(-) diff --git a/Dockerfile.php-8.1 b/Dockerfile.php-8.1 index 7b73d35e203..321055524d6 100644 --- a/Dockerfile.php-8.1 +++ b/Dockerfile.php-8.1 @@ -14,23 +14,6 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql -RUN \ - apk update \ - && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ - && rm -rf /var/cache/apk/* - -RUN \ - # Redis Extension - git clone https://github.com/phpredis/phpredis.git && \ - cd phpredis && \ - git checkout $PHP_REDIS_VERSION && \ - phpize && \ - ./configure && \ - make && make install && \ - cd .. - -RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini - WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index 8f3ee8a6539..8c2d4babc98 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -14,24 +14,6 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql - -RUN \ - apk update \ - && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ - && rm -rf /var/cache/apk/* - -RUN \ - # Redis Extension - git clone https://github.com/phpredis/phpredis.git && \ - cd phpredis && \ - git checkout $PHP_REDIS_VERSION && \ - phpize && \ - ./configure && \ - make && make install && \ - cd .. - -RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini - WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index b749a718969..63f979822ce 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -14,24 +14,6 @@ LABEL maintainer="team@appwrite.io" RUN docker-php-ext-install pdo_mysql - -RUN \ - apk update \ - && apk add --no-cache make automake autoconf gcc g++ git zlib-dev libmemcached-dev \ - && rm -rf /var/cache/apk/* - -RUN \ - # Redis Extension - git clone https://github.com/phpredis/phpredis.git && \ - cd phpredis && \ - git checkout $PHP_REDIS_VERSION && \ - phpize && \ - ./configure && \ - make && make install && \ - cd .. - -RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini - WORKDIR /code COPY --from=step0 /src/vendor /code/vendor diff --git a/docker-compose.yml b/docker-compose.yml index d4ffa4e8862..3e2b3faa355 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: networks: - abuse depends_on: + - redis - mysql volumes: - ./phpunit.xml:/code/phpunit.xml From b3105b361311d601069697c7691517d70f1bd32d Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:13:52 -0400 Subject: [PATCH 258/470] chore: merge --- composer.lock | 170 +++++++++++++++------------- src/Abuse/Adapters/Database.php | 77 ++++++++++++- tests/Abuse/Bench/DatabaseBench.php | 2 +- tests/Abuse/DatabaseTest.php | 2 +- tests/Abuse/RedisTest.php | 2 +- 5 files changed, 166 insertions(+), 87 deletions(-) diff --git a/composer.lock b/composer.lock index e2fb9e98a59..d6512ad5466 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": "1e53afffe12ae95f750f420f449e4c19", + "content-hash": "9a0fc06bf721ea900f18ce30dc40b628", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.50.0", + "version": "dev-feat-framework-v2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" + "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", + "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", "shasum": "" }, "require": { @@ -283,7 +283,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "0.33.*", + "utopia-php/framework": "0.34.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -294,7 +294,7 @@ "phpunit/phpunit": "^9.4", "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.14.0" + "utopia-php/cli": "0.17.*" }, "type": "library", "autoload": { @@ -316,54 +316,58 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.50.0" + "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" }, - "time": "2024-06-21T03:21:42+00:00" + "time": "2024-06-06T00:07:47+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.6", + "version": "0.34.3", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" + "reference": "e3bbca07c1df4e908ea9d3ce4f59367a7696b66b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "url": "https://api.github.com/repos/utopia-php/http/zipball/e3bbca07c1df4e908ea9d3ce4f59367a7696b66b", + "reference": "e3bbca07c1df4e908ea9d3ce4f59367a7696b66b", "shasum": "" }, "require": { + "ext-swoole": "*", "php": ">=8.0" }, "require-dev": { "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\": "src/", + "Tests\\E2E\\": "tests/e2e" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A simple, light and advanced PHP framework", + "description": "A simple, light and advanced PHP HTTP framework", "keywords": [ "framework", + "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.6" + "source": "https://github.com/utopia-php/http/tree/0.34.3" }, - "time": "2024-03-21T18:10:57+00:00" + "time": "2024-07-02T15:08:46+00:00" }, { "name": "utopia-php/mongo", @@ -778,16 +782,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -798,7 +802,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -830,9 +834,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -1056,16 +1060,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.2.15", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c" + "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/f7000319695cfad04a57fc64bf7ef7abdf4c437c", - "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", + "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", "shasum": "" }, "require": { @@ -1077,29 +1081,30 @@ "ext-spl": "*", "ext-tokenizer": "*", "php": "^8.1", - "phpbench/container": "^2.1", + "phpbench/container": "^2.2", "phpbench/dom": "~0.3.3", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/finder": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/process": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^6.1 || ^7.0", + "symfony/filesystem": "^6.1 || ^7.0", + "symfony/finder": "^6.1 || ^7.0", + "symfony/options-resolver": "^6.1 || ^7.0", + "symfony/process": "^6.1 || ^7.0", "webmozart/glob": "^4.6" }, "require-dev": { "dantleech/invoke": "^2.0", + "ergebnis/composer-normalize": "^2.39", "friendsofphp/php-cs-fixer": "^3.0", "jangregor/phpstan-prophecy": "^1.0", "phpspec/prophecy": "dev-master", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.0", - "rector/rector": "^0.18.10", - "symfony/error-handler": "^5.2 || ^6.0 || ^7.0", - "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^10.4", + "rector/rector": "^0.18.11 || ^1.0.0", + "symfony/error-handler": "^6.1 || ^7.0", + "symfony/var-dumper": "^6.1 || ^7.0" }, "suggest": { "ext-xdebug": "For Xdebug profiling extension." @@ -1142,7 +1147,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.2.15" + "source": "https://github.com/phpbench/phpbench/tree/1.3.1" }, "funding": [ { @@ -1150,20 +1155,20 @@ "type": "github" } ], - "time": "2023-11-29T12:21:11+00:00" + "time": "2024-06-30T11:04:37+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.11.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", "shasum": "" }, "require": { @@ -1208,7 +1213,7 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-07-06T11:17:41+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1531,45 +1536,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1614,7 +1619,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -1630,7 +1635,7 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "psr/cache", @@ -2749,23 +2754,23 @@ }, { "name": "seld/jsonlint", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.5", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "bin": [ @@ -2797,7 +2802,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { @@ -2809,7 +2814,7 @@ "type": "tidelift" } ], - "time": "2024-02-07T12:57:50+00:00" + "time": "2024-07-11T14:55:45+00:00" }, { "name": "symfony/console", @@ -3817,9 +3822,18 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/database", + "version": "dev-feat-framework-v2", + "alias": "0.49.99", + "alias_normalized": "0.49.99.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Abuse/Adapters/Database.php b/src/Abuse/Adapters/Database.php index 534ee49904c..b4c8a8be14d 100644 --- a/src/Abuse/Adapters/Database.php +++ b/src/Abuse/Adapters/Database.php @@ -9,8 +9,7 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; -use Utopia\Exception; +use Utopia\Http\Exception; class Database extends TimeLimit { @@ -127,7 +126,7 @@ protected function count(string $key, string $datetime): int } /** @var array $result */ - $result = Authorization::skip(function () use ($key, $datetime) { + $result = $this->db->getAuthorization()->skip(function () use ($key, $datetime) { return $this->db->find(Database::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -159,7 +158,7 @@ protected function hit(string $key, string $datetime): void return; } - Authorization::skip(function () use ($datetime, $key) { + $this->db->getAuthorization()->skip(function () use ($datetime, $key) { $data = $this->db->findOne(Database::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -216,7 +215,7 @@ protected function hit(string $key, string $datetime): void public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ - $results = Authorization::skip(function () use ($offset, $limit) { + $results = $this->db->getAuthorization()->skip(function () use ($offset, $limit) { $queries = []; $queries[] = Query::orderDesc(''); @@ -243,7 +242,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(string $datetime): bool { - Authorization::skip(function () use ($datetime) { + $this->db->getAuthorization()->skip(function () use ($datetime) { do { $documents = $this->db->find(Database::COLLECTION, [ Query::lessThan('time', $datetime), @@ -257,4 +256,70 @@ public function cleanup(string $datetime): bool return true; } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit. limit 0 is equal to unlimited + * + * @return bool + * + * @throws \Throwable + */ + public function check(): bool + { + if (0 == $this->limit) { + return false; + } + + $key = $this->parseKey(); + + if ($this->limit > $this->count($key, $this->time)) { + $this->hit($key, $this->time); + + return false; + } + + return true; + } + + /** + * Remaining + * + * Returns the number of current remaining counts + * + * @return int + * + * @throws \Exception + */ + public function remaining(): int + { + $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done + + return (0 > $left) ? 0 : $left; + } + + /** + * Limit + * + * Return the limit integer + * + * @return int + */ + public function limit(): int + { + return $this->limit; + } + + /** + * Time + * + * Return the Datetime + * + * @return string + */ + public function time(): string + { + return $this->time; + } } diff --git a/tests/Abuse/Bench/DatabaseBench.php b/tests/Abuse/Bench/DatabaseBench.php index fcba9517fd4..833d25396c9 100644 --- a/tests/Abuse/Bench/DatabaseBench.php +++ b/tests/Abuse/Bench/DatabaseBench.php @@ -10,7 +10,7 @@ use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; -use Utopia\Exception; +use Utopia\Http\Exception; class DatabaseBench extends Base { diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index 5927d0f72fc..7def059f891 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -12,7 +12,7 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Exception; +use Utopia\Http\Exception; class DatabaseTest extends Base { diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index b29b5680852..4f938635dc8 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -7,7 +7,7 @@ use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\Redis; use Redis as Client; -use Utopia\Exception; +use Utopia\Http\Exception; class RedisTest extends Base { From 4f49bbba8235dc2ed8220482e1e1b46f1e4598df Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:35:18 -0400 Subject: [PATCH 259/470] chore: updating to latest database --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 593015fe285..1a9f23284d0 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "dev-feat-framework-v2 as 0.49.99" + "utopia-php/database": "0.51.*" }, "require-dev": { "phpunit/phpunit": "^9.4", From 7ff88da05716acb20a2ca479635a677c40e8ca1c Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:49:58 -0400 Subject: [PATCH 260/470] chore: updating to the latest database --- composer.json | 4 +- composer.lock | 211 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 154 insertions(+), 61 deletions(-) diff --git a/composer.json b/composer.json index 1a9f23284d0..8842ebb9648 100755 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "keywords": ["php","framework", "upf", "utopia", "abuse"], "license": "MIT", - "minimum-stability": "stable", + "minimum-stability": "RC", "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.51.*" + "utopia-php/database": "1.0.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index d6512ad5466..26b23d6fb62 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": "9a0fc06bf721ea900f18ce30dc40b628", + "content-hash": "e6a6611ef5abc949f69ba822126ffd75", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-framework-v2", + "version": "1.0.0-RC1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a" + "reference": "36720e88125e3c16fb770bf2422a757fcec68050" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", - "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/36720e88125e3c16fb770bf2422a757fcec68050", + "reference": "36720e88125e3c16fb770bf2422a757fcec68050", "shasum": "" }, "require": { @@ -283,7 +283,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "0.34.*", + "utopia-php/framework": "1.0.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -294,7 +294,7 @@ "phpunit/phpunit": "^9.4", "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "0.17.*" + "utopia-php/cli": "1.0.*" }, "type": "library", "autoload": { @@ -316,29 +316,79 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/database/tree/1.0.0-RC1" }, - "time": "2024-06-06T00:07:47+00:00" + "time": "2024-08-09T17:41:07+00:00" + }, + { + "name": "utopia-php/di", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/di.git", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/", + "Tests\\E2E\\": "tests/e2e" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple and lite library for managing dependency injections", + "keywords": [ + "framework", + "http", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/di/issues", + "source": "https://github.com/utopia-php/di/tree/0.1.0" + }, + "time": "2024-08-08T14:35:19+00:00" }, { "name": "utopia-php/framework", - "version": "0.34.3", + "version": "1.0.0-RC2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "e3bbca07c1df4e908ea9d3ce4f59367a7696b66b" + "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/e3bbca07c1df4e908ea9d3ce4f59367a7696b66b", - "reference": "e3bbca07c1df4e908ea9d3ce4f59367a7696b66b", + "url": "https://api.github.com/repos/utopia-php/http/zipball/d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", + "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", "shasum": "" }, "require": { "ext-swoole": "*", - "php": ">=8.0" + "php": ">=8.0", + "utopia-php/servers": "0.1.* " }, "require-dev": { + "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", @@ -348,8 +398,7 @@ "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/", - "Tests\\E2E\\": "tests/e2e" + "Utopia\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -365,9 +414,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.34.3" + "source": "https://github.com/utopia-php/http/tree/1.0.0-RC2" }, - "time": "2024-07-02T15:08:46+00:00" + "time": "2024-08-08T14:46:41+00:00" }, { "name": "utopia-php/mongo", @@ -428,6 +477,59 @@ "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, "time": "2023-09-01T17:25:28+00:00" + }, + { + "name": "utopia-php/servers", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/servers.git", + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/servers/zipball/7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "utopia-php/di": "0.1.*" + }, + "require-dev": { + "laravel/pint": "^0.2.3", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Servers\\": "src/Servers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "A base library for building Utopia style servers.", + "keywords": [ + "framework", + "php", + "servers", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/servers/issues", + "source": "https://github.com/utopia-php/servers/tree/0.1.0" + }, + "time": "2024-08-08T14:31:39+00:00" } ], "packages-dev": [ @@ -1159,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.11.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "640410b32995914bde3eed26fa89552f9c2c082f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", + "reference": "640410b32995914bde3eed26fa89552f9c2c082f", "shasum": "" }, "require": { @@ -1213,7 +1315,7 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-08-08T09:02:50+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2818,16 +2920,16 @@ }, { "name": "symfony/console", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { @@ -2891,7 +2993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.2" + "source": "https://github.com/symfony/console/tree/v7.1.3" }, "funding": [ { @@ -2907,7 +3009,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3044,16 +3146,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", + "reference": "717c6329886f32dc65e27461f80f2a465412fdca", "shasum": "" }, "require": { @@ -3088,7 +3190,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.1" + "source": "https://github.com/symfony/finder/tree/v7.1.3" }, "funding": [ { @@ -3104,7 +3206,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/options-resolver", @@ -3493,16 +3595,16 @@ }, { "name": "symfony/process", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { @@ -3534,7 +3636,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.1" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -3550,7 +3652,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/service-contracts", @@ -3637,16 +3739,16 @@ }, { "name": "symfony/string", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { @@ -3704,7 +3806,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.2" + "source": "https://github.com/symfony/string/tree/v7.1.3" }, "funding": [ { @@ -3720,7 +3822,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:27:18+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "theseer/tokenizer", @@ -3822,18 +3924,9 @@ "time": "2024-03-07T20:33:40+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-framework-v2", - "alias": "0.49.99", - "alias_normalized": "0.49.99.0" - } - ], - "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "aliases": [], + "minimum-stability": "RC", + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From 42db504f69fc0b37a646e0f370d430675127d466 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 16 Aug 2024 18:05:08 +1200 Subject: [PATCH 261/470] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8e932b52565..1a9f23284d0 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.50.*" + "utopia-php/database": "0.51.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index e2fb9e98a59..2dd290058d9 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": "1e53afffe12ae95f750f420f449e4c19", + "content-hash": "986175a38bba7f2c617617df88b8972a", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.50.0", + "version": "0.51.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" + "reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", - "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/cf723c720e5aa938709e74f1ccfc3b46ecdc6de3", + "reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.50.0" + "source": "https://github.com/utopia-php/database/tree/0.51.0" }, - "time": "2024-06-21T03:21:42+00:00" + "time": "2024-08-16T03:28:04+00:00" }, { "name": "utopia-php/framework", From d3036739baf62f5150c2fc0397b7829e43e37cf8 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 19 Aug 2024 14:41:47 +1200 Subject: [PATCH 262/470] Update namespaces to better reflect adapters --- .../{Database.php => Database/TimeLimit.php} | 27 ++++++++++--------- .../{Redis.php => Redis/TimeLimit.php} | 5 ++-- .../TimeLimitBench.php} | 9 ++++--- .../TimeLimitBench.php} | 9 ++++--- tests/Abuse/DatabaseTest.php | 2 +- tests/Abuse/RedisTest.php | 10 +++---- 6 files changed, 33 insertions(+), 29 deletions(-) rename src/Abuse/Adapters/{Database.php => Database/TimeLimit.php} (86%) rename src/Abuse/Adapters/{Redis.php => Redis/TimeLimit.php} (96%) rename tests/Abuse/Bench/{DatabaseBench.php => Database/TimeLimitBench.php} (82%) rename tests/Abuse/Bench/{RedisBench.php => Redis/TimeLimitBench.php} (56%) diff --git a/src/Abuse/Adapters/Database.php b/src/Abuse/Adapters/Database/TimeLimit.php similarity index 86% rename from src/Abuse/Adapters/Database.php rename to src/Abuse/Adapters/Database/TimeLimit.php index 534ee49904c..0d408295edb 100644 --- a/src/Abuse/Adapters/Database.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -1,7 +1,8 @@ db->createCollection(Database::COLLECTION, $attributes, $indexes); + $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); } catch (Duplicate) { // Collection already exists } @@ -128,7 +129,7 @@ protected function count(string $key, string $datetime): int /** @var array $result */ $result = Authorization::skip(function () use ($key, $datetime) { - return $this->db->find(Database::COLLECTION, [ + return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -160,7 +161,7 @@ protected function hit(string $key, string $datetime): void } Authorization::skip(function () use ($datetime, $key) { - $data = $this->db->findOne(Database::COLLECTION, [ + $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -171,14 +172,14 @@ protected function hit(string $key, string $datetime): void 'key' => $key, 'time' => $datetime, 'count' => 1, - '$collection' => Database::COLLECTION, + '$collection' => TimeLimit::COLLECTION, ]; try { - $this->db->createDocument(Database::COLLECTION, new Document($data)); + $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); } catch (Duplicate $e) { // Duplicate in case of race condition - $data = $this->db->findOne(Database::COLLECTION, [ + $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -188,14 +189,14 @@ protected function hit(string $key, string $datetime): void if (\is_numeric($count)) { $this->count = intval($count); } - $this->db->increaseDocumentAttribute(Database::COLLECTION, $data->getId(), 'count'); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } else { throw new \Exception('Document Not Found'); } } } else { /** @var Document $data */ - $this->db->increaseDocumentAttribute(Database::COLLECTION, $data->getId(), 'count'); + $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); } }); @@ -227,7 +228,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $queries[] = Query::limit($limit); } - return $this->db->find(Database::COLLECTION, $queries); + return $this->db->find(TimeLimit::COLLECTION, $queries); }); return $results; @@ -245,12 +246,12 @@ public function cleanup(string $datetime): bool { Authorization::skip(function () use ($datetime) { do { - $documents = $this->db->find(Database::COLLECTION, [ + $documents = $this->db->find(TimeLimit::COLLECTION, [ Query::lessThan('time', $datetime), ]); foreach ($documents as $document) { - $this->db->deleteDocument(Database::COLLECTION, $document->getId()); + $this->db->deleteDocument(TimeLimit::COLLECTION, $document->getId()); } } while (! empty($documents)); }); diff --git a/src/Abuse/Adapters/Redis.php b/src/Abuse/Adapters/Redis/TimeLimit.php similarity index 96% rename from src/Abuse/Adapters/Redis.php rename to src/Abuse/Adapters/Redis/TimeLimit.php index d9b0892dd50..f2349212b30 100644 --- a/src/Abuse/Adapters/Redis.php +++ b/src/Abuse/Adapters/Redis/TimeLimit.php @@ -1,10 +1,11 @@ setNamespace('namespace'); $this->db = $db; - $adapter = new AdaptersDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); diff --git a/tests/Abuse/Bench/RedisBench.php b/tests/Abuse/Bench/Redis/TimeLimitBench.php similarity index 56% rename from tests/Abuse/Bench/RedisBench.php rename to tests/Abuse/Bench/Redis/TimeLimitBench.php index b3ad310d08a..0d8cdb771db 100644 --- a/tests/Abuse/Bench/RedisBench.php +++ b/tests/Abuse/Bench/Redis/TimeLimitBench.php @@ -1,12 +1,13 @@ redis = new Client(); $this->redis->connect('redis', 6379); - $this->adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $this->adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); $this->abuse = new Abuse($this->adapter); } } diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index 5927d0f72fc..b3820e6aa4a 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -5,7 +5,7 @@ use PDO; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\Database as AdaptersDatabase; +use Utopia\Abuse\Adapters\Database\TimeLimit as AdaptersDatabase; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index b29b5680852..517ee947777 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -3,10 +3,10 @@ namespace Utopia\Tests; use DateInterval; -use Utopia\Abuse\Adapter; -use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\Redis; use Redis as Client; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapter; +use Utopia\Abuse\Adapters\Redis\TimeLimit; use Utopia\Exception; class RedisTest extends Base @@ -21,14 +21,14 @@ public function setUp(): void { $this->redis = new Client(); $this->redis->connect('redis', 6379); - $adapter = new Redis('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); } public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new Redis($key, $limit, $seconds, $this->redis); + return new TimeLimit($key, $limit, $seconds, $this->redis); } public function getCleanupDateTime(): string From 25f0683d9ca7217d33df6dfc8e17ebd9e0416045 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 21 Aug 2024 20:14:07 +1200 Subject: [PATCH 263/470] Update database --- composer.json | 2 +- composer.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 1a9f23284d0..a711be8f6d8 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.51.*" + "utopia-php/database": "0.52.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 2dd290058d9..9205a3ddb3a 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": "986175a38bba7f2c617617df88b8972a", + "content-hash": "87249976d07043fb41e2794ea14d081d", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.51.0", + "version": "0.52.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3" + "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/cf723c720e5aa938709e74f1ccfc3b46ecdc6de3", - "reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0b48921dd5e9e07529983f954cf987e7d4461f6e", + "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e", "shasum": "" }, "require": { @@ -287,14 +287,14 @@ "utopia-php/mongo": "0.3.*" }, "require-dev": { - "fakerphp/faker": "^1.14", - "laravel/pint": "1.13.*", - "pcov/clobber": "^2.0", - "phpstan/phpstan": "1.10.*", - "phpunit/phpunit": "^9.4", - "rregeer/phpunit-coverage-check": "^0.3.1", - "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "^0.14.0" + "fakerphp/faker": "1.23.*", + "laravel/pint": "1.17.*", + "pcov/clobber": "2.0.*", + "phpstan/phpstan": "1.11.*", + "phpunit/phpunit": "9.6.*", + "rregeer/phpunit-coverage-check": "0.3.*", + "swoole/ide-helper": "5.1.3", + "utopia-php/cli": "0.14.*" }, "type": "library", "autoload": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.51.0" + "source": "https://github.com/utopia-php/database/tree/0.52.0" }, - "time": "2024-08-16T03:28:04+00:00" + "time": "2024-08-21T08:11:14+00:00" }, { "name": "utopia-php/framework", From 2a94103f28da57a4cc1e424dea836b7a998a0531 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 30 Aug 2024 14:11:06 +0900 Subject: [PATCH 264/470] Chore: Update Database Lib --- composer.json | 2 +- composer.lock | 237 +++++++++++++++++++++++++------------------------- 2 files changed, 120 insertions(+), 119 deletions(-) diff --git a/composer.json b/composer.json index a711be8f6d8..6283871b3a3 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.52.*" + "utopia-php/database": "0.53.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 9205a3ddb3a..152575410fb 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": "87249976d07043fb41e2794ea14d081d", + "content-hash": "6522b9fb8652f6801bd7c5664430c672", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.52.0", + "version": "0.53.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e" + "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0b48921dd5e9e07529983f954cf987e7d4461f6e", - "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2e116d544b14e8b2edfcb5b793d214136ff64f42", + "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42", "shasum": "" }, "require": { @@ -316,22 +316,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.52.0" + "source": "https://github.com/utopia-php/database/tree/0.53.0" }, - "time": "2024-08-21T08:11:14+00:00" + "time": "2024-08-30T04:32:56+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.6", + "version": "0.33.8", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", - "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", "shasum": "" }, "require": { @@ -361,9 +361,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.6" + "source": "https://github.com/utopia-php/http/tree/0.33.8" }, - "time": "2024-03-21T18:10:57+00:00" + "time": "2024-08-15T14:10:09+00:00" }, { "name": "utopia-php/mongo", @@ -778,16 +778,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -798,7 +798,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -830,9 +830,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -1056,16 +1056,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.2.15", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c" + "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/f7000319695cfad04a57fc64bf7ef7abdf4c437c", - "reference": "f7000319695cfad04a57fc64bf7ef7abdf4c437c", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", + "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", "shasum": "" }, "require": { @@ -1077,29 +1077,30 @@ "ext-spl": "*", "ext-tokenizer": "*", "php": "^8.1", - "phpbench/container": "^2.1", + "phpbench/container": "^2.2", "phpbench/dom": "~0.3.3", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/finder": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0", - "symfony/process": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^6.1 || ^7.0", + "symfony/filesystem": "^6.1 || ^7.0", + "symfony/finder": "^6.1 || ^7.0", + "symfony/options-resolver": "^6.1 || ^7.0", + "symfony/process": "^6.1 || ^7.0", "webmozart/glob": "^4.6" }, "require-dev": { "dantleech/invoke": "^2.0", + "ergebnis/composer-normalize": "^2.39", "friendsofphp/php-cs-fixer": "^3.0", "jangregor/phpstan-prophecy": "^1.0", "phpspec/prophecy": "dev-master", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.0", - "rector/rector": "^0.18.10", - "symfony/error-handler": "^5.2 || ^6.0 || ^7.0", - "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^10.4", + "rector/rector": "^0.18.11 || ^1.0.0", + "symfony/error-handler": "^6.1 || ^7.0", + "symfony/var-dumper": "^6.1 || ^7.0" }, "suggest": { "ext-xdebug": "For Xdebug profiling extension." @@ -1142,7 +1143,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.2.15" + "source": "https://github.com/phpbench/phpbench/tree/1.3.1" }, "funding": [ { @@ -1150,20 +1151,20 @@ "type": "github" } ], - "time": "2023-11-29T12:21:11+00:00" + "time": "2024-06-30T11:04:37+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "384af967d35b2162f69526c7276acadce534d0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", + "reference": "384af967d35b2162f69526c7276acadce534d0e1", "shasum": "" }, "require": { @@ -1208,39 +1209,39 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-08-27T09:18:05+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "9.2.32", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", + "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^3.0.6", + "phpunit/php-text-template": "^2.0.4", + "sebastian/code-unit-reverse-lookup": "^2.0.3", + "sebastian/complexity": "^2.0.3", + "sebastian/environment": "^5.1.5", + "sebastian/lines-of-code": "^1.0.4", + "sebastian/version": "^3.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -1249,7 +1250,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -1278,7 +1279,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" }, "funding": [ { @@ -1286,7 +1287,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:23:01+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1531,45 +1532,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1614,7 +1615,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -1630,7 +1631,7 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "psr/cache", @@ -1736,16 +1737,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "79dff0b268932c640297f5208d6298f71855c03e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", "shasum": "" }, "require": { @@ -1780,9 +1781,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.1" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-08-21T13:31:24+00:00" }, { "name": "sebastian/cli-parser", @@ -2749,23 +2750,23 @@ }, { "name": "seld/jsonlint", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", - "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.5", + "phpstan/phpstan": "^1.11", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, "bin": [ @@ -2797,7 +2798,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" }, "funding": [ { @@ -2809,20 +2810,20 @@ "type": "tidelift" } ], - "time": "2024-02-07T12:57:50+00:00" + "time": "2024-07-11T14:55:45+00:00" }, { "name": "symfony/console", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { @@ -2886,7 +2887,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.2" + "source": "https://github.com/symfony/console/tree/v7.1.3" }, "funding": [ { @@ -2902,7 +2903,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3039,16 +3040,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", + "reference": "717c6329886f32dc65e27461f80f2a465412fdca", "shasum": "" }, "require": { @@ -3083,7 +3084,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.1" + "source": "https://github.com/symfony/finder/tree/v7.1.3" }, "funding": [ { @@ -3099,7 +3100,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/options-resolver", @@ -3488,16 +3489,16 @@ }, { "name": "symfony/process", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { @@ -3529,7 +3530,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.1" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -3545,7 +3546,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/service-contracts", @@ -3632,16 +3633,16 @@ }, { "name": "symfony/string", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { @@ -3699,7 +3700,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.2" + "source": "https://github.com/symfony/string/tree/v7.1.3" }, "funding": [ { @@ -3715,7 +3716,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:27:18+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "theseer/tokenizer", From 0059ae5daec97edd85276bb5c7eb501484b79ad5 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:14:29 -0400 Subject: [PATCH 265/470] chore: merge, lint --- composer.lock | 190 +++++++++++++++++----- src/Abuse/Adapters/Database/TimeLimit.php | 4 +- 2 files changed, 150 insertions(+), 44 deletions(-) diff --git a/composer.lock b/composer.lock index 152575410fb..03a120a02e0 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": "6522b9fb8652f6801bd7c5664430c672", + "content-hash": "e6a6611ef5abc949f69ba822126ffd75", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.0", + "version": "1.0.0-RC2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42" + "reference": "d70eb76c7cf5ee596752209a4d57e4d08fe8a986" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2e116d544b14e8b2edfcb5b793d214136ff64f42", - "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d70eb76c7cf5ee596752209a4d57e4d08fe8a986", + "reference": "d70eb76c7cf5ee596752209a4d57e4d08fe8a986", "shasum": "" }, "require": { @@ -283,7 +283,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "0.33.*", + "utopia-php/framework": "1.0.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -294,7 +294,7 @@ "phpunit/phpunit": "9.6.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.14.*" + "utopia-php/cli": "1.0.*" }, "type": "library", "autoload": { @@ -316,32 +316,84 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.0" + "source": "https://github.com/utopia-php/database/tree/1.0.0-RC2" }, - "time": "2024-08-30T04:32:56+00:00" + "time": "2024-09-04T17:58:16+00:00" + }, + { + "name": "utopia-php/di", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/di.git", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/", + "Tests\\E2E\\": "tests/e2e" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple and lite library for managing dependency injections", + "keywords": [ + "framework", + "http", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/di/issues", + "source": "https://github.com/utopia-php/di/tree/0.1.0" + }, + "time": "2024-08-08T14:35:19+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.8", + "version": "1.0.0-RC2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" + "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", + "url": "https://api.github.com/repos/utopia-php/http/zipball/d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", + "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", "shasum": "" }, "require": { - "php": ">=8.0" + "ext-swoole": "*", + "php": ">=8.0", + "utopia-php/servers": "0.1.* " }, "require-dev": { + "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "phpunit/phpunit": "^9.5.25", + "swoole/ide-helper": "4.8.3" }, "type": "library", "autoload": { @@ -353,17 +405,18 @@ "license": [ "MIT" ], - "description": "A simple, light and advanced PHP framework", + "description": "A simple, light and advanced PHP HTTP framework", "keywords": [ "framework", + "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.8" + "source": "https://github.com/utopia-php/http/tree/1.0.0-RC2" }, - "time": "2024-08-15T14:10:09+00:00" + "time": "2024-08-08T14:46:41+00:00" }, { "name": "utopia-php/mongo", @@ -424,6 +477,59 @@ "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, "time": "2023-09-01T17:25:28+00:00" + }, + { + "name": "utopia-php/servers", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/servers.git", + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/servers/zipball/7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "utopia-php/di": "0.1.*" + }, + "require-dev": { + "laravel/pint": "^0.2.3", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Servers\\": "src/Servers" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "A base library for building Utopia style servers.", + "keywords": [ + "framework", + "php", + "servers", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/servers/issues", + "source": "https://github.com/utopia-php/servers/tree/0.1.0" + }, + "time": "2024-08-08T14:31:39+00:00" } ], "packages-dev": [ @@ -1155,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "384af967d35b2162f69526c7276acadce534d0e1" + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", - "reference": "384af967d35b2162f69526c7276acadce534d0e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", "shasum": "" }, "require": { @@ -1209,7 +1315,7 @@ "type": "github" } ], - "time": "2024-08-27T09:18:05+00:00" + "time": "2024-09-03T19:55:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2814,16 +2920,16 @@ }, { "name": "symfony/console", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", "shasum": "" }, "require": { @@ -2887,7 +2993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.3" + "source": "https://github.com/symfony/console/tree/v7.1.4" }, "funding": [ { @@ -2903,7 +3009,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3040,16 +3146,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca" + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", "shasum": "" }, "require": { @@ -3084,7 +3190,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.3" + "source": "https://github.com/symfony/finder/tree/v7.1.4" }, "funding": [ { @@ -3100,7 +3206,7 @@ "type": "tidelift" } ], - "time": "2024-07-24T07:08:44+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/options-resolver", @@ -3633,16 +3739,16 @@ }, { "name": "symfony/string", - "version": "v7.1.3", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -3700,7 +3806,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.3" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -3716,7 +3822,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:25:37+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "theseer/tokenizer", @@ -3819,7 +3925,7 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "RC", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index e5940936f04..2527442cfb6 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -128,7 +128,7 @@ protected function count(string $key, string $datetime): int /** @var array $result */ $result = $this->db->getAuthorization()->skip(function () use ($key, $datetime) { - return $this->db->find(Database::COLLECTION, [ + return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -160,7 +160,7 @@ protected function hit(string $key, string $datetime): void } $this->db->getAuthorization()->skip(function () use ($datetime, $key) { - $data = $this->db->findOne(Database::COLLECTION, [ + $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); From 40080354a1b3c972b1680dfe27e6dbeb6556c0da Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:05:55 -0400 Subject: [PATCH 266/470] chore: updating to latest database --- composer.json | 2 +- composer.lock | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index 8842ebb9648..ce4baf2f2bb 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "1.0.*" + "utopia-php/database": "0.54.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 03a120a02e0..8ab1de626eb 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": "e6a6611ef5abc949f69ba822126ffd75", + "content-hash": "7601e6576cde00888812fb6e2e732dbe", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "1.0.0-RC2", + "version": "0.54.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d70eb76c7cf5ee596752209a4d57e4d08fe8a986" + "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d70eb76c7cf5ee596752209a4d57e4d08fe8a986", - "reference": "d70eb76c7cf5ee596752209a4d57e4d08fe8a986", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1e97fc8b212a8daf9b9a68244677ed34c9db143e", + "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e", "shasum": "" }, "require": { @@ -294,7 +294,7 @@ "phpunit/phpunit": "9.6.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "1.0.*" + "utopia-php/cli": "0.19.*" }, "type": "library", "autoload": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.0.0-RC2" + "source": "https://github.com/utopia-php/database/tree/0.54.0" }, - "time": "2024-09-04T17:58:16+00:00" + "time": "2024-09-05T16:00:42+00:00" }, { "name": "utopia-php/di", @@ -370,22 +370,22 @@ }, { "name": "utopia-php/framework", - "version": "1.0.0-RC2", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7" + "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", - "reference": "d1e9674dbf33bed03fa53854ec1f2c6e074cf4d7", + "url": "https://api.github.com/repos/utopia-php/http/zipball/cc880ec41f7f163d4f9956fec26cc6be51b412cf", + "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf", "shasum": "" }, "require": { "ext-swoole": "*", "php": ">=8.0", - "utopia-php/servers": "0.1.* " + "utopia-php/servers": "0.1.*" }, "require-dev": { "ext-xdebug": "*", @@ -414,9 +414,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/1.0.0-RC2" + "source": "https://github.com/utopia-php/http/tree/1.0.0" }, - "time": "2024-08-08T14:46:41+00:00" + "time": "2024-09-05T15:38:08+00:00" }, { "name": "utopia-php/mongo", @@ -535,16 +535,16 @@ "packages-dev": [ { "name": "doctrine/annotations", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", "shasum": "" }, "require": { @@ -556,10 +556,10 @@ "require-dev": { "doctrine/cache": "^2.0", "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", + "phpstan/phpstan": "^1.10.28", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" + "symfony/cache": "^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" }, "suggest": { "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" @@ -605,9 +605,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" + "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, - "time": "2023-02-02T22:02:53+00:00" + "time": "2024-09-05T10:17:24+00:00" }, { "name": "doctrine/instantiator", From 6f0a7dc02a167c67fd3d8bfc298f9ff9b000a7c4 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Thu, 5 Sep 2024 12:08:08 -0400 Subject: [PATCH 267/470] chore: changing to stable --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ce4baf2f2bb..823856bc1b9 100755 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "keywords": ["php","framework", "upf", "utopia", "abuse"], "license": "MIT", - "minimum-stability": "RC", + "minimum-stability": "stable", "autoload": { "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} }, From 1c7d4680b5d9a385a88c6b2f5622b3d4b3611bea Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 17 Sep 2024 18:20:53 +0900 Subject: [PATCH 268/470] Chore: findOne now returns empty documents instead of false on miss --- src/Abuse/Adapters/Database/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index 2527442cfb6..0f1e779295c 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -165,7 +165,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data === false) { + if ($data->isEmpty()) { $data = [ '$permissions' => [], 'key' => $key, @@ -183,7 +183,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data !== false && $data instanceof Document) { + if (!$data->isEmpty()) { $count = $data->getAttribute('count', 0); if (\is_numeric($count)) { $this->count = intval($count); From 4198352039d096c258d01e8e1442126069eba48c Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 18 Sep 2024 13:15:57 +0900 Subject: [PATCH 269/470] Chore: Update Database Library --- composer.json | 2 +- composer.lock | 144 +++++++++++++++++++++++++------------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/composer.json b/composer.json index 823856bc1b9..79685c2ffaa 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.54.*" + "utopia-php/database": "0.55.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 8ab1de626eb..055b1ecad73 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": "7601e6576cde00888812fb6e2e732dbe", + "content-hash": "4ae74a41329275b52e4722de01c60285", "packages": [ { "name": "jean85/pretty-package-versions", @@ -136,20 +136,20 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -196,7 +196,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -212,7 +212,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "utopia-php/cache", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.54.0", + "version": "0.55", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e" + "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1e97fc8b212a8daf9b9a68244677ed34c9db143e", - "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0ad2eb1c163196cd79253c59f1896c5b03d6ba91", + "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.54.0" + "source": "https://github.com/utopia-php/database/tree/0.55" }, - "time": "2024-09-05T16:00:42+00:00" + "time": "2024-09-18T04:00:12+00:00" }, { "name": "utopia-php/di", @@ -370,16 +370,16 @@ }, { "name": "utopia-php/framework", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf" + "reference": "fc63ec61c720190a5ea5bb484c615145850951e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/cc880ec41f7f163d4f9956fec26cc6be51b412cf", - "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf", + "url": "https://api.github.com/repos/utopia-php/http/zipball/fc63ec61c720190a5ea5bb484c615145850951e6", + "reference": "fc63ec61c720190a5ea5bb484c615145850951e6", "shasum": "" }, "require": { @@ -414,9 +414,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/1.0.0" + "source": "https://github.com/utopia-php/http/tree/1.0.2" }, - "time": "2024-09-05T15:38:08+00:00" + "time": "2024-09-10T09:04:19+00:00" }, { "name": "utopia-php/mongo", @@ -480,16 +480,16 @@ }, { "name": "utopia-php/servers", - "version": "0.1.0", + "version": "0.1.1", "source": { "type": "git", "url": "https://github.com/utopia-php/servers.git", - "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c" + "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/servers/zipball/7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", - "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "url": "https://api.github.com/repos/utopia-php/servers/zipball/fd5c8d32778f265256c1936372a071b944f5ba8a", + "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a", "shasum": "" }, "require": { @@ -527,9 +527,9 @@ ], "support": { "issues": "https://github.com/utopia-php/servers/issues", - "source": "https://github.com/utopia-php/servers/tree/0.1.0" + "source": "https://github.com/utopia-php/servers/tree/0.1.1" }, - "time": "2024-08-08T14:31:39+00:00" + "time": "2024-09-06T02:25:56+00:00" } ], "packages-dev": [ @@ -884,16 +884,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", "shasum": "" }, "require": { @@ -936,9 +936,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-15T16:40:33+00:00" }, { "name": "phar-io/manifest", @@ -1261,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.1", + "version": "1.12.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", "shasum": "" }, "require": { @@ -1315,7 +1315,7 @@ "type": "github" } ], - "time": "2024-09-03T19:55:22+00:00" + "time": "2024-09-09T08:10:35+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1843,16 +1843,16 @@ }, { "name": "psr/log", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "79dff0b268932c640297f5208d6298f71855c03e" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", - "reference": "79dff0b268932c640297f5208d6298f71855c03e", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1887,9 +1887,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.1" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2024-08-21T13:31:24+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "sebastian/cli-parser", @@ -3277,20 +3277,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -3336,7 +3336,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -3352,24 +3352,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3414,7 +3414,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -3430,24 +3430,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3495,7 +3495,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -3511,24 +3511,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -3575,7 +3575,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -3591,7 +3591,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", @@ -3925,7 +3925,7 @@ } ], "aliases": [], - "minimum-stability": "RC", + "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, From b76dd683c3e2877251aca4b38fd9c015911066fe Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 7 Oct 2024 19:07:20 +0000 Subject: [PATCH 270/470] chore: update db library --- composer.lock | 96 +++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/composer.lock b/composer.lock index 055b1ecad73..82030d2556c 100644 --- a/composer.lock +++ b/composer.lock @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.55", + "version": "0.55.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91" + "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0ad2eb1c163196cd79253c59f1896c5b03d6ba91", - "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6f44079999491feb0ef85686f6b2ac7ef54db828", + "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.55" + "source": "https://github.com/utopia-php/database/tree/0.55.1" }, - "time": "2024-09-18T04:00:12+00:00" + "time": "2024-10-07T18:52:26+00:00" }, { "name": "utopia-php/di", @@ -884,16 +884,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.2.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -936,9 +936,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-09-15T16:40:33+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "phar-io/manifest", @@ -1261,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.3", + "version": "1.12.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", + "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", "shasum": "" }, "require": { @@ -1315,7 +1315,7 @@ "type": "github" } ], - "time": "2024-09-09T08:10:35+00:00" + "time": "2024-10-06T15:03:59+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1638,16 +1638,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.21", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", "shasum": "" }, "require": { @@ -1662,7 +1662,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -1721,7 +1721,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" }, "funding": [ { @@ -1737,7 +1737,7 @@ "type": "tidelift" } ], - "time": "2024-07-10T11:45:39+00:00" + "time": "2024-09-19T10:50:18+00:00" }, { "name": "psr/cache", @@ -2920,16 +2920,16 @@ }, { "name": "symfony/console", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", "shasum": "" }, "require": { @@ -2993,7 +2993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.4" + "source": "https://github.com/symfony/console/tree/v7.1.5" }, "funding": [ { @@ -3009,7 +3009,7 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:53+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3080,16 +3080,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", + "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", "shasum": "" }, "require": { @@ -3126,7 +3126,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v7.1.5" }, "funding": [ { @@ -3142,7 +3142,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-09-17T09:16:35+00:00" }, { "name": "symfony/finder", @@ -3595,16 +3595,16 @@ }, { "name": "symfony/process", - "version": "v7.1.3", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + "reference": "5c03ee6369281177f07f7c68252a280beccba847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", + "reference": "5c03ee6369281177f07f7c68252a280beccba847", "shasum": "" }, "require": { @@ -3636,7 +3636,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.3" + "source": "https://github.com/symfony/process/tree/v7.1.5" }, "funding": [ { @@ -3652,7 +3652,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:44:47+00:00" + "time": "2024-09-19T21:48:23+00:00" }, { "name": "symfony/service-contracts", @@ -3739,16 +3739,16 @@ }, { "name": "symfony/string", - "version": "v7.1.4", + "version": "v7.1.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", + "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", "shasum": "" }, "require": { @@ -3806,7 +3806,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.4" + "source": "https://github.com/symfony/string/tree/v7.1.5" }, "funding": [ { @@ -3822,7 +3822,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-09-20T08:28:38+00:00" }, { "name": "theseer/tokenizer", From e404c21e8dcf6a310bc83cf1d74e716b105598fa Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 23 Oct 2024 17:29:12 +1300 Subject: [PATCH 271/470] Expose attributes and indexes --- src/Abuse/Adapters/Database/TimeLimit.php | 101 ++++++++++++---------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index 0d408295edb..b6947539e81 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -17,6 +17,50 @@ class TimeLimit extends TimeLimitAdapter { public const COLLECTION = 'abuse'; + public const ATTRIBUTES = [ + [ + '$id' => 'key', + 'type' => UtopiaDB::VAR_STRING, + 'size' => UtopiaDB::LENGTH_KEY, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], [ + '$id' => 'time', + 'type' => UtopiaDB::VAR_DATETIME, + 'size' => 0, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => ['datetime'], + ], [ + '$id' => 'count', + 'type' => UtopiaDB::VAR_INTEGER, + 'size' => 11, + 'required' => true, + 'signed' => false, + 'array' => false, + 'filters' => [], + ], + ]; + + public const INDEXES = [ + [ + '$id' => 'unique1', + 'type' => UtopiaDB::INDEX_UNIQUE, + 'attributes' => ['key', 'time'], + 'lengths' => [], + 'orders' => [], + ], [ + '$id' => 'index2', + 'type' => UtopiaDB::INDEX_KEY, + 'attributes' => ['time'], + 'lengths' => [], + 'orders' => [], + ], + ]; + /** * @var UtopiaDB */ @@ -52,55 +96,20 @@ public function setup(): void throw new Exception('You need to create database before running timelimit setup'); } - $attributes = [ - new Document([ - '$id' => 'key', - 'type' => UtopiaDB::VAR_STRING, - 'size' => UtopiaDB::LENGTH_KEY, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ]), - new Document([ - '$id' => 'time', - 'type' => UtopiaDB::VAR_DATETIME, - 'size' => 0, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => ['datetime'], - ]), - new Document([ - '$id' => 'count', - 'type' => UtopiaDB::VAR_INTEGER, - 'size' => 11, - 'required' => true, - 'signed' => false, - 'array' => false, - 'filters' => [], - ]), - ]; + $attributes = \array_map(function ($attribute) { + return new Document($attribute); + }, self::ATTRIBUTES); - $indexes = [ - new Document([ - '$id' => 'unique1', - 'type' => UtopiaDB::INDEX_UNIQUE, - 'attributes' => ['key', 'time'], - 'lengths' => [], - 'orders' => [], - ]), - new Document([ - '$id' => 'index2', - 'type' => UtopiaDB::INDEX_KEY, - 'attributes' => ['time'], - 'lengths' => [], - 'orders' => [], - ]), - ]; + $indexes = \array_map(function ($index) { + return new Document($index); + }, self::INDEXES); try { - $this->db->createCollection(TimeLimit::COLLECTION, $attributes, $indexes); + $this->db->createCollection( + TimeLimit::COLLECTION, + $attributes, + $indexes + ); } catch (Duplicate) { // Collection already exists } From 27cd99bac027e2d47f07b3036a01fb6872eeb416 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 17 Sep 2024 18:20:53 +0900 Subject: [PATCH 272/470] Chore: findOne now returns empty documents instead of false on miss --- src/Abuse/Adapters/Database/TimeLimit.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index b6947539e81..47f7e899b7d 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -175,7 +175,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data === false) { + if ($data->isEmpty()) { $data = [ '$permissions' => [], 'key' => $key, @@ -193,7 +193,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data !== false && $data instanceof Document) { + if (!$data->isEmpty()) { $count = $data->getAttribute('count', 0); if (\is_numeric($count)) { $this->count = intval($count); From 8aec34dc830f717efd6c130f3338d6771a44fa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 12 Dec 2024 19:39:38 +0000 Subject: [PATCH 273/470] Update dependencies --- composer.json | 2 +- composer.lock | 2612 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 2083 insertions(+), 531 deletions(-) diff --git a/composer.json b/composer.json index 6283871b3a3..5edc270736c 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.53.*" + "utopia-php/database": "0.53.200" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 152575410fb..f8481d3db80 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,217 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6522b9fb8652f6801bd7c5664430c672", + "content-hash": "99ca13df0ed5cb02c170e711cc847511", "packages": [ + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "google/protobuf", + "version": "v4.29.1", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", + "reference": "6042b5483f8029e42473faeb8ef75ba266278381", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" + }, + "time": "2024-12-03T22:07:45+00:00" + }, { "name": "jean85/pretty-package-versions", - "version": "2.0.6", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", "extra": { @@ -61,9 +246,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" }, - "time": "2024-03-08T09:58:59+00:00" + "time": "2024-11-18T16:19:46+00:00" }, { "name": "mongodb/mongodb", @@ -135,38 +320,1589 @@ "time": "2021-10-20T22:22:37+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "name": "nyholm/psr7", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "open-telemetry/api", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/api.git", + "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", + "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", + "shasum": "" + }, + "require": { + "open-telemetry/context": "^1.0", + "php": "^8.1", + "psr/log": "^1.1|^2.0|^3.0", + "symfony/polyfill-php82": "^1.26" + }, + "conflict": { + "open-telemetry/sdk": "<=1.0.8" + }, + "type": "library", + "extra": { + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + }, + "branch-alias": { + "dev-main": "1.1.x-dev" + } + }, + "autoload": { + "files": [ + "Trace/functions.php" + ], + "psr-4": { + "OpenTelemetry\\API\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "API for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "api", + "apm", + "logging", + "opentelemetry", + "otel", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-11-16T04:32:30+00:00" + }, + { + "name": "open-telemetry/context", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/context.git", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^8.1", + "symfony/polyfill-php82": "^1.26" + }, + "suggest": { + "ext-ffi": "To allow context switching in Fibers" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "fiber/initialize_fiber_handler.php" + ], + "psr-4": { + "OpenTelemetry\\Context\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Context implementation for OpenTelemetry PHP.", + "keywords": [ + "Context", + "opentelemetry", + "otel" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-08-21T00:29:20+00:00" + }, + { + "name": "open-telemetry/exporter-otlp", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/exporter-otlp.git", + "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "shasum": "" + }, + "require": { + "open-telemetry/api": "^1.0", + "open-telemetry/gen-otlp-protobuf": "^1.1", + "open-telemetry/sdk": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + } + }, + "autoload": { + "files": [ + "_register.php" + ], + "psr-4": { + "OpenTelemetry\\Contrib\\Otlp\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "OTLP exporter for OpenTelemetry.", + "keywords": [ + "Metrics", + "exporter", + "gRPC", + "http", + "opentelemetry", + "otel", + "otlp", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-04-30T18:28:30+00:00" + }, + { + "name": "open-telemetry/gen-otlp-protobuf", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", + "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66c3b98e998a726691c92e6405a82e6e7b8b169d", + "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d", + "shasum": "" + }, + "require": { + "google/protobuf": "^3.22 || ^4.0", + "php": "^8.0" + }, + "suggest": { + "ext-protobuf": "For better performance, when dealing with the protobuf format" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opentelemetry\\Proto\\": "Opentelemetry/Proto/", + "GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.", + "keywords": [ + "Metrics", + "apm", + "gRPC", + "logging", + "opentelemetry", + "otel", + "otlp", + "protobuf", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-10-30T11:49:49+00:00" + }, + { + "name": "open-telemetry/sdk", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sdk.git", + "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nyholm/psr7-server": "^1.1", + "open-telemetry/api": "~1.0 || ~1.1", + "open-telemetry/context": "^1.0", + "open-telemetry/sem-conv": "^1.0", + "php": "^8.1", + "php-http/discovery": "^1.14", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/http-message": "^1.0.1|^2.0", + "psr/log": "^1.1|^2.0|^3.0", + "ramsey/uuid": "^3.0 || ^4.0", + "symfony/polyfill-mbstring": "^1.23", + "symfony/polyfill-php82": "^1.26", + "tbachert/spi": "^1.0.1" + }, + "suggest": { + "ext-gmp": "To support unlimited number of synchronous metric readers", + "ext-mbstring": "To increase performance of string operations", + "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" + }, + "spi": { + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ + "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" + ] + } + }, + "autoload": { + "files": [ + "Common/Util/functions.php", + "Logs/Exporter/_register.php", + "Metrics/MetricExporter/_register.php", + "Propagation/_register.php", + "Trace/SpanExporter/_register.php", + "Common/Dev/Compatibility/_load.php", + "_autoload.php" + ], + "psr-4": { + "OpenTelemetry\\SDK\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "SDK for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "sdk", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-10-18T21:01:35+00:00" + }, + { + "name": "open-telemetry/sem-conv", + "version": "1.27.1", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/sem-conv.git", + "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/1dba705fea74bc0718d04be26090e3697e56f4e6", + "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\SemConv\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "description": "Semantic conventions for OpenTelemetry PHP.", + "keywords": [ + "Metrics", + "apm", + "logging", + "opentelemetry", + "otel", + "semantic conventions", + "semconv", + "tracing" + ], + "support": { + "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", + "docs": "https://opentelemetry.io/docs/php", + "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", + "source": "https://github.com/open-telemetry/opentelemetry-php" + }, + "time": "2024-08-28T09:20:31+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:50:44+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:49:48+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php82", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php82.git", + "reference": "5d2ed36f7734637dacc025f179698031951b1692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", + "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php82\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" + "Symfony\\Contracts\\Service\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -174,10 +1910,6 @@ "MIT" ], "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -187,16 +1919,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -212,20 +1946,72 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "tbachert/spi", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Nevay/spi.git", + "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", + "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "composer/semver": "^1.0 || ^2.0 || ^3.0", + "php": "^8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "infection/infection": "^0.27.9", + "phpunit/phpunit": "^10.5", + "psalm/phar": "^5.18" + }, + "type": "composer-plugin", + "extra": { + "class": "Nevay\\SPI\\Composer\\Plugin", + "branch-alias": { + "dev-main": "0.2.x-dev" + }, + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Nevay\\SPI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Service provider loading facility", + "keywords": [ + "service provider" + ], + "support": { + "issues": "https://github.com/Nevay/spi/issues", + "source": "https://github.com/Nevay/spi/tree/v1.0.2" + }, + "time": "2024-10-04T16:36:12+00:00" }, { "name": "utopia-php/cache", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247" + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ebcab5aac7606331cef69b0081f6c9eff2e58bc", + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc", "shasum": "" }, "require": { @@ -236,7 +2022,7 @@ }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -260,38 +2046,84 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.10.2" + "source": "https://github.com/utopia-php/cache/tree/0.11.0" + }, + "time": "2024-11-05T16:53:58+00:00" + }, + { + "name": "utopia-php/compression", + "version": "0.1.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/compression.git", + "reference": "6062f70596415f8d5de40a589367b0eb2a435f98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/compression/zipball/6062f70596415f8d5de40a589367b0eb2a435f98", + "reference": "6062f70596415f8d5de40a589367b0eb2a435f98", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpunit/phpunit": "^9.3", + "vimeo/psalm": "4.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Compression\\": "src/Compression" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple Compression library to handle file compression", + "keywords": [ + "compression", + "framework", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/compression/issues", + "source": "https://github.com/utopia-php/compression/tree/0.1.2" }, - "time": "2024-06-25T20:36:35+00:00" + "time": "2024-11-08T14:59:54+00:00" }, { "name": "utopia-php/database", - "version": "0.53.0", + "version": "0.53.200", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42" + "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2e116d544b14e8b2edfcb5b793d214136ff64f42", - "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42", + "url": "https://api.github.com/repos/utopia-php/database/zipball/570c63a3760d0e1404679ddfacd9484af40bd9fc", + "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", - "php": ">=8.0", - "utopia-php/cache": "0.10.*", + "php": ">=8.3", + "utopia-php/cache": "0.11.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.17.*", - "pcov/clobber": "2.0.*", - "phpstan/phpstan": "1.11.*", - "phpunit/phpunit": "9.6.*", + "laravel/pint": "1.*", + "pcov/clobber": "2.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "9.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", "utopia-php/cli": "0.14.*" @@ -316,26 +2148,28 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.0" + "source": "https://github.com/utopia-php/database/tree/0.53.200" }, - "time": "2024-08-30T04:32:56+00:00" + "time": "2024-12-01T07:59:15+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.8", + "version": "0.33.15", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" + "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", - "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", + "url": "https://api.github.com/repos/utopia-php/http/zipball/83b0628900c2c53e8c3efbf069f3e13050295edc", + "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc", "shasum": "" }, "require": { - "php": ">=8.0" + "php": ">=8.1", + "utopia-php/compression": "0.1.*", + "utopia-php/telemetry": "0.1.*" }, "require-dev": { "laravel/pint": "^1.2", @@ -361,9 +2195,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.8" + "source": "https://github.com/utopia-php/http/tree/0.33.15" }, - "time": "2024-08-15T14:10:09+00:00" + "time": "2024-12-10T13:07:04+00:00" }, { "name": "utopia-php/mongo", @@ -424,21 +2258,71 @@ "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, "time": "2023-09-01T17:25:28+00:00" + }, + { + "name": "utopia-php/telemetry", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/telemetry.git", + "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "shasum": "" + }, + "require": { + "ext-opentelemetry": "*", + "ext-protobuf": "*", + "nyholm/psr7": "^1.8", + "open-telemetry/exporter-otlp": "^1.1", + "open-telemetry/sdk": "^1.1", + "php": ">=8.0", + "symfony/http-client": "^7.1" + }, + "require-dev": { + "laravel/pint": "^1.2", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "keywords": [ + "framework", + "php", + "upf" + ], + "support": { + "issues": "https://github.com/utopia-php/telemetry/issues", + "source": "https://github.com/utopia-php/telemetry/tree/0.1.0" + }, + "time": "2024-11-13T10:29:53+00:00" } ], "packages-dev": [ { "name": "doctrine/annotations", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", "shasum": "" }, "require": { @@ -450,10 +2334,10 @@ "require-dev": { "doctrine/cache": "^2.0", "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", + "phpstan/phpstan": "^1.10.28", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" + "symfony/cache": "^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" }, "suggest": { "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" @@ -499,9 +2383,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" + "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, - "time": "2023-02-02T22:02:53+00:00" + "time": "2024-09-05T10:17:24+00:00" }, { "name": "doctrine/instantiator", @@ -718,16 +2602,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -766,7 +2650,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -774,20 +2658,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -830,9 +2714,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", @@ -1052,6 +2936,7 @@ "issues": "https://github.com/phpbench/dom/issues", "source": "https://github.com/phpbench/dom/tree/0.3.3" }, + "abandoned": true, "time": "2023-03-06T23:46:57+00:00" }, { @@ -1155,16 +3040,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.0", + "version": "1.12.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "384af967d35b2162f69526c7276acadce534d0e1" + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", - "reference": "384af967d35b2162f69526c7276acadce534d0e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", "shasum": "" }, "require": { @@ -1209,7 +3094,7 @@ "type": "github" } ], - "time": "2024-08-27T09:18:05+00:00" + "time": "2024-11-28T22:13:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1532,16 +3417,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.20", + "version": "9.6.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "49d7820565836236411f5dc002d16dd689cde42f" + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", - "reference": "49d7820565836236411f5dc002d16dd689cde42f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "shasum": "" }, "require": { @@ -1552,11 +3437,11 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-code-coverage": "^9.2.32", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -1605,148 +3490,46 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2024-07-10T11:45:39+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" }, - "time": "2021-11-05T16:47:00+00:00" + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-12-05T13:48:26+00:00" }, { - "name": "psr/log", - "version": "3.0.1", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "79dff0b268932c640297f5208d6298f71855c03e" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", - "reference": "79dff0b268932c640297f5208d6298f71855c03e", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { @@ -1755,12 +3538,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Cache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1773,17 +3556,16 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Common interface for caching libraries", "keywords": [ - "log", + "cache", "psr", - "psr-3" + "psr-6" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.1" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2024-08-21T13:31:24+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "sebastian/cli-parser", @@ -2814,16 +4596,16 @@ }, { "name": "symfony/console", - "version": "v7.1.3", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -2887,74 +4669,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-07-26T12:41:01+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -2970,20 +4685,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/filesystem", - "version": "v7.1.2", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", - "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", "shasum": "" }, "require": { @@ -3020,7 +4735,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" }, "funding": [ { @@ -3036,20 +4751,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-10-25T15:15:23+00:00" }, { "name": "symfony/finder", - "version": "v7.1.3", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca" + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca", + "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", "shasum": "" }, "require": { @@ -3084,7 +4799,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.3" + "source": "https://github.com/symfony/finder/tree/v7.2.0" }, "funding": [ { @@ -3100,20 +4815,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T07:08:44+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { @@ -3151,7 +4866,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -3167,24 +4882,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -3195,8 +4910,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3230,7 +4945,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -3246,24 +4961,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3271,8 +4986,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3308,7 +5023,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -3324,24 +5039,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -3349,8 +5064,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -3389,87 +5104,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T15:07:36+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -3485,20 +5120,20 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v7.1.3", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { @@ -3530,90 +5165,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-07-26T12:44:47+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.5.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/process/tree/v7.2.0" }, "funding": [ { @@ -3629,20 +5181,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/string", - "version": "v7.1.3", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { @@ -3700,7 +5252,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.3" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -3716,7 +5268,7 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:25:37+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "theseer/tokenizer", @@ -3820,7 +5372,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -3829,6 +5381,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } From 572c41680a6c2ef9530e468d9a9fffe66a3dceb2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 17 Dec 2024 20:34:51 +0530 Subject: [PATCH 274/470] feat: add support for redis cluster --- composer.lock | 6 +- docker-compose.yml | 48 ++++++ phpunit.xml | 2 +- src/Abuse/Adapter.php | 4 +- src/Abuse/Adapters/TimeLimit.php | 2 - .../TimeLimit.php => TimeLimit/Database.php} | 28 ++-- .../TimeLimit.php => TimeLimit/Redis.php} | 15 +- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 153 ++++++++++++++++++ tests/Abuse/Base.php | 2 +- tests/Abuse/DatabaseTest.php | 7 +- tests/Abuse/RedisClusterTest.php | 39 +++++ tests/Abuse/RedisTest.php | 7 +- 12 files changed, 276 insertions(+), 37 deletions(-) rename src/Abuse/Adapters/{Database/TimeLimit.php => TimeLimit/Database.php} (87%) rename src/Abuse/Adapters/{Redis/TimeLimit.php => TimeLimit/Redis.php} (90%) create mode 100644 src/Abuse/Adapters/TimeLimit/RedisCluster.php create mode 100644 tests/Abuse/RedisClusterTest.php diff --git a/composer.lock b/composer.lock index f8481d3db80..1769d6c2fb4 100644 --- a/composer.lock +++ b/composer.lock @@ -758,13 +758,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - }, "spi": { "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] + }, + "branch-alias": { + "dev-main": "1.0.x-dev" } }, "autoload": { diff --git a/docker-compose.yml b/docker-compose.yml index 3e2b3faa355..0599ca8545b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,5 +30,53 @@ services: - ./src:/code/src - ./tests:/code/tests + redis-insight: + image: redis/redisinsight:latest + restart: unless-stopped + networks: + - abuse + environment: + - REDIS_HOSTS=redis-cluster-0:6379 + ports: + - "8081:5540" + + redis-cluster-0: + image: docker.io/bitnami/redis-cluster:7.4 + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 + - REDIS_CLUSTER_CREATOR=yes + - REDIS_CLUSTER_REPLICAS=0 + networks: + - abuse + depends_on: + - redis-cluster-1 + - redis-cluster-2 + - redis-cluster-3 + + redis-cluster-1: + image: docker.io/bitnami/redis-cluster:7.4 + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 + networks: + - abuse + + redis-cluster-2: + image: docker.io/bitnami/redis-cluster:7.4 + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 + networks: + - abuse + + redis-cluster-3: + image: docker.io/bitnami/redis-cluster:7.4 + environment: + - ALLOW_EMPTY_PASSWORD=yes + - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 + networks: + - abuse + networks: abuse: \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index ec39a7edeaf..10d408caa08 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 4af27dc40a3..d78a06f9e2d 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -70,11 +70,11 @@ protected function parseKey(): string * * Return logs with an offset and limit * - * @param int|null $offset + * @param int $offset * @param int|null $limit * @return array */ - abstract public function getLogs(?int $offset = null, ?int $limit = 25): array; + abstract public function getLogs(int $offset = 0, ?int $limit = 25): array; /** * Delete all logs older than $datetime diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 8040dc3b9ae..dcafee08353 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -37,8 +37,6 @@ abstract protected function count(string $key, string $datetime): int; abstract protected function hit(string $key, string $datetime): void; - - /** * Check * diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/TimeLimit/Database.php similarity index 87% rename from src/Abuse/Adapters/Database/TimeLimit.php rename to src/Abuse/Adapters/TimeLimit/Database.php index 47f7e899b7d..a7ba5662c29 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -1,8 +1,8 @@ db->createCollection( - TimeLimit::COLLECTION, + self::COLLECTION, $attributes, $indexes ); @@ -138,7 +138,7 @@ protected function count(string $key, string $datetime): int /** @var array $result */ $result = Authorization::skip(function () use ($key, $datetime) { - return $this->db->find(TimeLimit::COLLECTION, [ + return $this->db->find(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -170,7 +170,7 @@ protected function hit(string $key, string $datetime): void } Authorization::skip(function () use ($datetime, $key) { - $data = $this->db->findOne(TimeLimit::COLLECTION, [ + $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -181,14 +181,14 @@ protected function hit(string $key, string $datetime): void 'key' => $key, 'time' => $datetime, 'count' => 1, - '$collection' => TimeLimit::COLLECTION, + '$collection' => self::COLLECTION, ]; try { - $this->db->createDocument(TimeLimit::COLLECTION, new Document($data)); + $this->db->createDocument(self::COLLECTION, new Document($data)); } catch (Duplicate $e) { // Duplicate in case of race condition - $data = $this->db->findOne(TimeLimit::COLLECTION, [ + $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), ]); @@ -198,14 +198,14 @@ protected function hit(string $key, string $datetime): void if (\is_numeric($count)) { $this->count = intval($count); } - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); + $this->db->increaseDocumentAttribute(self::COLLECTION, $data->getId(), 'count'); } else { throw new \Exception('Document Not Found'); } } } else { /** @var Document $data */ - $this->db->increaseDocumentAttribute(TimeLimit::COLLECTION, $data->getId(), 'count'); + $this->db->increaseDocumentAttribute(self::COLLECTION, $data->getId(), 'count'); } }); @@ -237,7 +237,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $queries[] = Query::limit($limit); } - return $this->db->find(TimeLimit::COLLECTION, $queries); + return $this->db->find(self::COLLECTION, $queries); }); return $results; @@ -255,12 +255,12 @@ public function cleanup(string $datetime): bool { Authorization::skip(function () use ($datetime) { do { - $documents = $this->db->find(TimeLimit::COLLECTION, [ + $documents = $this->db->find(self::COLLECTION, [ Query::lessThan('time', $datetime), ]); foreach ($documents as $document) { - $this->db->deleteDocument(TimeLimit::COLLECTION, $document->getId()); + $this->db->deleteDocument(self::COLLECTION, $document->getId()); } } while (! empty($documents)); }); diff --git a/src/Abuse/Adapters/Redis/TimeLimit.php b/src/Abuse/Adapters/TimeLimit/Redis.php similarity index 90% rename from src/Abuse/Adapters/Redis/TimeLimit.php rename to src/Abuse/Adapters/TimeLimit/Redis.php index f2349212b30..1fbef435979 100644 --- a/src/Abuse/Adapters/Redis/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -1,11 +1,11 @@ */ - public function getLogs(?int $offset = null, ?int $limit = 25): array + public function getLogs(int $offset = 0, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset - $cursor = null; - $keys = $this->redis->scan($cursor, self::NAMESPACE . '__*', $limit); + $keys = $this->redis->scan($offset, self::NAMESPACE . '__*', $limit); if (!$keys) { return []; } @@ -110,7 +109,7 @@ public function cleanup(string $datetime): bool { $iterator = null; while ($iterator !== 0) { - $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 1000); + $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 10); $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); $this->redis->del($keys); } diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php new file mode 100644 index 00000000000..426dc22fd57 --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -0,0 +1,153 @@ +redis = $redis; + $this->key = $key; + $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); + $this->time = strval($time); + $this->limit = $limit; + } + + /** + * Undocumented function + * + * @param string $key + * @param string $datetime + * @return integer + */ + protected function count(string $key, string $datetime): int + { + if (0 == $this->limit) { // No limit no point for counting + return 0; + } + + if (! \is_null($this->count)) { // Get fetched result + return $this->count; + } + + /** @var string $count */ + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + if (!$count) { + $this->count = 0; + } else { + $this->count = intval($count); + } + + return $this->count; + } + + /** + * @param string $key + * @param string $datetime + * @return void + * + */ + protected function hit(string $key, string $datetime): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + /** @var string $count */ + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + if (!$count) { + $this->count = 0; + } else { + $this->count = intval($count); + } + + $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $datetime); + $this->count++; + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int $offset + * @param int|null $limit + * @return array + */ + public function getLogs(int $offset = 0, ?int $limit = 25): array + { + // TODO limit potential is SCAN but needs cursor no offset + $cursor = null; + $keys = $this->scan($cursor, self::NAMESPACE . '__*', $limit); + if (!$keys) { + return []; + } + + $logs = []; + foreach ($keys as $key) { + $logs[$key] = $this->redis->get($key); + } + return $logs; + } + + /** + * Delete all logs older than $datetime + * + * @param string $datetime + * @return bool + */ + public function cleanup(string $datetime): bool + { + $iterator = null; + $keys = $this->scan($iterator, self::NAMESPACE . '__*__*', 1000); + $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); + $this->redis->del($keys); + return true; + } + + /** + * Filter keys + * + * @param array $keys + * @param integer $timestamp + * @return array + */ + protected function filterKeys(array $keys, int $timestamp): array + { + $filteredKeys = []; + foreach ($keys as $key) { + $parts = explode('__', $key); + $keyTimestamp = (int)end($parts); // Assuming the last part is always the timestamp + if ($keyTimestamp < $timestamp) { + $filteredKeys[] = $key; + } + } + return $filteredKeys; + } + + protected function scan(&$testing, string $pattern, int $count): array|bool + { + $matches = []; + foreach ($this->redis->_masters() as $master) { + $cursor = null; + do { + $keys = $this->redis->scan($cursor, $master, $pattern, $count); + if ($keys !== false) { + $matches = array_merge($matches, $keys); + } + } while ($cursor > 0); + } + + return empty($matches) ? false : $matches; + } +} diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 1b88f411f61..1eb9f0b7d3c 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -63,7 +63,7 @@ public function testCleanup(): void $status = $this->abuse->cleanup($this->getCleanupDateTime()); $this->assertEquals($status, true); - // Check that there are no logs in the DB + // // Check that there are no logs in the DB $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(0, \count($logs)); } diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index b3820e6aa4a..3890da5fa4d 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -5,7 +5,7 @@ use PDO; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\Database\TimeLimit as AdaptersDatabase; +use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimitDatabase; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; @@ -37,7 +37,7 @@ public function setUp(): void $db->setNamespace('namespace'); $this->db = $db; - $adapter = new AdaptersDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + $adapter = new TimeLimitDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -45,11 +45,12 @@ public function setUp(): void $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); + $this->abuse->cleanup($this->getCleanupDateTime()); } public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new AdaptersDatabase($key, $limit, $seconds, $this->db); + return new TimeLimitDatabase($key, $limit, $seconds, $this->db); } public function getCleanupDateTime(): string diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php new file mode 100644 index 00000000000..92876704793 --- /dev/null +++ b/tests/Abuse/RedisClusterTest.php @@ -0,0 +1,39 @@ +redis = new RedisCluster(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); + $adapter = new TimeLimitRedisCluster('login-attempt-from-{{ip}}', 3, 1, $this->redis); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $this->abuse = new Abuse($adapter); + $this->abuse->cleanup($this->getCleanupDateTime()); + } + + public function getAdapter(string $key, int $limit, int $seconds): Adapter + { + return new TimeLimitRedisCluster($key, $limit, $seconds, $this->redis); + } + + public function getCleanupDateTime(): string + { + $interval = DateInterval::createFromDateString(1 . ' seconds'); + return strval((new \DateTime())->sub($interval)->getTimestamp()); + } +} diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 517ee947777..b39bc953c8f 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -6,7 +6,7 @@ use Redis as Client; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\Redis\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\Exception; class RedisTest extends Base @@ -21,14 +21,15 @@ public function setUp(): void { $this->redis = new Client(); $this->redis->connect('redis', 6379); - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $adapter = new TimeLimitRedis('login-attempt-from-{{ip}}', 3, 1, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); + $this->abuse->cleanup($this->getCleanupDateTime()); } public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new TimeLimit($key, $limit, $seconds, $this->redis); + return new TimeLimitRedis($key, $limit, $seconds, $this->redis); } public function getCleanupDateTime(): string From 3b78c863cc8590f24b064eb56e8aa519f31a65e0 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 17 Dec 2024 20:38:02 +0530 Subject: [PATCH 275/470] feat: cleanup --- src/Abuse/Adapters/TimeLimit/Redis.php | 7 ++++--- tests/Abuse/Base.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 1fbef435979..11cbd4747cb 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -80,13 +80,14 @@ protected function hit(string $key, string $datetime): void * * Return logs with an offset and limit * - * @param int $offset + * @param int|null $offset * @param int|null $limit * @return array */ - public function getLogs(int $offset = 0, ?int $limit = 25): array + public function getLogs(?int $offset = null, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset + $cursor = null; $keys = $this->redis->scan($offset, self::NAMESPACE . '__*', $limit); if (!$keys) { return []; @@ -109,7 +110,7 @@ public function cleanup(string $datetime): bool { $iterator = null; while ($iterator !== 0) { - $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 10); + $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 1000); $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); $this->redis->del($keys); } diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 1eb9f0b7d3c..1b88f411f61 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -63,7 +63,7 @@ public function testCleanup(): void $status = $this->abuse->cleanup($this->getCleanupDateTime()); $this->assertEquals($status, true); - // // Check that there are no logs in the DB + // Check that there are no logs in the DB $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(0, \count($logs)); } From 9b338b6d06a5bb8ea0c824e36abc51128e7ea292 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 17 Dec 2024 20:39:50 +0530 Subject: [PATCH 276/470] feat: cleanup --- src/Abuse/Adapter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index d78a06f9e2d..4af27dc40a3 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -70,11 +70,11 @@ protected function parseKey(): string * * Return logs with an offset and limit * - * @param int $offset + * @param int|null $offset * @param int|null $limit * @return array */ - abstract public function getLogs(int $offset = 0, ?int $limit = 25): array; + abstract public function getLogs(?int $offset = null, ?int $limit = 25): array; /** * Delete all logs older than $datetime From 1dfcdaca0e7cb86e4482bda77f43bf40bc8be4d0 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 17 Dec 2024 20:41:16 +0530 Subject: [PATCH 277/470] feat: cleanup --- src/Abuse/Adapters/TimeLimit/Redis.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 11cbd4747cb..783f5884edd 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -88,7 +88,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset $cursor = null; - $keys = $this->redis->scan($offset, self::NAMESPACE . '__*', $limit); + $keys = $this->redis->scan($cursor, self::NAMESPACE . '__*', $limit); if (!$keys) { return []; } From aa15eda877a17364e3184b0b7bb4ffbb3f081736 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 17 Dec 2024 20:41:56 +0530 Subject: [PATCH 278/470] feat: cleanup --- tests/Abuse/DatabaseTest.php | 6 +++--- tests/Abuse/RedisClusterTest.php | 6 +++--- tests/Abuse/RedisTest.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index 3890da5fa4d..f4a9252caa1 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -5,7 +5,7 @@ use PDO; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimitDatabase; +use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimit; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; @@ -37,7 +37,7 @@ public function setUp(): void $db->setNamespace('namespace'); $this->db = $db; - $adapter = new TimeLimitDatabase('login-attempt-from-{{ip}}', 3, 60 * 5, $db); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); if (! $db->exists('utopiaTests')) { $db->create(); $adapter->setup(); @@ -50,7 +50,7 @@ public function setUp(): void public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new TimeLimitDatabase($key, $limit, $seconds, $this->db); + return new TimeLimit($key, $limit, $seconds, $this->db); } public function getCleanupDateTime(): string diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php index 92876704793..3d39c56667b 100644 --- a/tests/Abuse/RedisClusterTest.php +++ b/tests/Abuse/RedisClusterTest.php @@ -6,7 +6,7 @@ use RedisCluster; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as TimeLimitRedisCluster; +use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as TimeLimit; use Utopia\Exception; class RedisClusterTest extends Base @@ -20,7 +20,7 @@ class RedisClusterTest extends Base public function setUp(): void { $this->redis = new RedisCluster(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); - $adapter = new TimeLimitRedisCluster('login-attempt-from-{{ip}}', 3, 1, $this->redis); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); $this->abuse->cleanup($this->getCleanupDateTime()); @@ -28,7 +28,7 @@ public function setUp(): void public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new TimeLimitRedisCluster($key, $limit, $seconds, $this->redis); + return new TimeLimit($key, $limit, $seconds, $this->redis); } public function getCleanupDateTime(): string diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index b39bc953c8f..8423db86084 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -6,7 +6,7 @@ use Redis as Client; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; +use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimit; use Utopia\Exception; class RedisTest extends Base @@ -21,7 +21,7 @@ public function setUp(): void { $this->redis = new Client(); $this->redis->connect('redis', 6379); - $adapter = new TimeLimitRedis('login-attempt-from-{{ip}}', 3, 1, $this->redis); + $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); $this->abuse->cleanup($this->getCleanupDateTime()); @@ -29,7 +29,7 @@ public function setUp(): void public function getAdapter(string $key, int $limit, int $seconds): Adapter { - return new TimeLimitRedis($key, $limit, $seconds, $this->redis); + return new TimeLimit($key, $limit, $seconds, $this->redis); } public function getCleanupDateTime(): string From 76fc3da33bea752165a34911ba6d16a04fca2dd7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 10:46:03 +0530 Subject: [PATCH 279/470] fix: codeql --- composer.lock | 10 ++-- src/Abuse/Adapters/TimeLimit/Redis.php | 7 ++- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 54 +++++++++++-------- tests/Abuse/Bench/Database/TimeLimitBench.php | 2 +- tests/Abuse/Bench/Redis/TimeLimitBench.php | 2 +- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/composer.lock b/composer.lock index 1769d6c2fb4..0ec24c8c21c 100644 --- a/composer.lock +++ b/composer.lock @@ -3040,16 +3040,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.12", + "version": "1.12.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" + "reference": "9b469068840cfa031e1deaf2fa1886d00e20680f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b469068840cfa031e1deaf2fa1886d00e20680f", + "reference": "9b469068840cfa031e1deaf2fa1886d00e20680f", "shasum": "" }, "require": { @@ -3094,7 +3094,7 @@ "type": "github" } ], - "time": "2024-11-28T22:13:23+00:00" + "time": "2024-12-17T17:00:20+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 783f5884edd..3657a570d5c 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -2,7 +2,6 @@ namespace Utopia\Abuse\Adapters\TimeLimit; -use Redis as Client; use Utopia\Abuse\Adapters\TimeLimit; class Redis extends TimeLimit @@ -10,11 +9,11 @@ class Redis extends TimeLimit public const NAMESPACE = 'abuse'; /** - * @var Client + * @var \Redis */ - protected Client $redis; + protected \Redis $redis; - public function __construct(string $key, int $limit, int $seconds, Client $redis) + public function __construct(string $key, int $limit, int $seconds, \Redis $redis) { $this->redis = $redis; $this->key = $key; diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 426dc22fd57..24145d2b4ba 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -2,7 +2,6 @@ namespace Utopia\Abuse\Adapters\TimeLimit; -use RedisCluster as Client; use Utopia\Abuse\Adapters\TimeLimit; class RedisCluster extends TimeLimit @@ -10,11 +9,11 @@ class RedisCluster extends TimeLimit public const NAMESPACE = 'abuse'; /** - * @var Client + * @var \RedisCluster */ - protected Client $redis; + protected \RedisCluster $redis; - public function __construct(string $key, int $limit, int $seconds, Client $redis) + public function __construct(string $key, int $limit, int $seconds, \RedisCluster $redis) { $this->redis = $redis; $this->key = $key; @@ -23,8 +22,8 @@ public function __construct(string $key, int $limit, int $seconds, Client $redis $this->limit = $limit; } - /** - * Undocumented function + /** + * Get count for a key at specific datetime * * @param string $key * @param string $datetime @@ -40,9 +39,9 @@ protected function count(string $key, string $datetime): int return $this->count; } - /** @var string $count */ + /** @var string|false $count */ $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); - if (!$count) { + if ($count === false) { $this->count = 0; } else { $this->count = intval($count); @@ -52,10 +51,11 @@ protected function count(string $key, string $datetime): int } /** - * @param string $key - * @param string $datetime - * @return void + * Record a hit for a key at specific datetime * + * @param string $key + * @param string $datetime + * @return void */ protected function hit(string $key, string $datetime): void { @@ -63,9 +63,9 @@ protected function hit(string $key, string $datetime): void return; } - /** @var string $count */ + /** @var string|false $count */ $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); - if (!$count) { + if ($count === false) { $this->count = 0; } else { $this->count = intval($count); @@ -80,16 +80,15 @@ protected function hit(string $key, string $datetime): void * * Return logs with an offset and limit * - * @param int $offset - * @param int|null $limit + * @param int|null $offset + * @param int|null $limit * @return array */ - public function getLogs(int $offset = 0, ?int $limit = 25): array + public function getLogs(?int $offset = 0, ?int $limit = 25): array { // TODO limit potential is SCAN but needs cursor no offset - $cursor = null; - $keys = $this->scan($cursor, self::NAMESPACE . '__*', $limit); - if (!$keys) { + $keys = $this->scan(self::NAMESPACE . '__*', $offset, $limit); + if ($keys === false) { return []; } @@ -103,14 +102,14 @@ public function getLogs(int $offset = 0, ?int $limit = 25): array /** * Delete all logs older than $datetime * - * @param string $datetime + * @param string $datetime * @return bool */ public function cleanup(string $datetime): bool { - $iterator = null; - $keys = $this->scan($iterator, self::NAMESPACE . '__*__*', 1000); + $keys = $this->scan(self::NAMESPACE . '__*__*'); $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); + /** @phpstan-ignore-next-line */ $this->redis->del($keys); return true; } @@ -135,12 +134,21 @@ protected function filterKeys(array $keys, int $timestamp): array return $filteredKeys; } - protected function scan(&$testing, string $pattern, int $count): array|bool + /** + * Scan keys across all masters in the Redis cluster + * + * @param string $pattern Pattern to match keys + * @param int|null $cursor Reference to the cursor for scanning + * @param int|null $count Number of keys to return per iteration + * @return array|false + */ + protected function scan(string $pattern, ?int &$cursor = null, ?int $count = 1000): array|false { $matches = []; foreach ($this->redis->_masters() as $master) { $cursor = null; do { + /** @phpstan-ignore-next-line */ $keys = $this->redis->scan($cursor, $master, $pattern, $count); if ($keys !== false) { $matches = array_merge($matches, $keys); diff --git a/tests/Abuse/Bench/Database/TimeLimitBench.php b/tests/Abuse/Bench/Database/TimeLimitBench.php index e2a110c6b7b..0525c44d817 100644 --- a/tests/Abuse/Bench/Database/TimeLimitBench.php +++ b/tests/Abuse/Bench/Database/TimeLimitBench.php @@ -4,7 +4,7 @@ use PDO; use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\Database\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimit; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; diff --git a/tests/Abuse/Bench/Redis/TimeLimitBench.php b/tests/Abuse/Bench/Redis/TimeLimitBench.php index 0d8cdb771db..3fdccfb811f 100644 --- a/tests/Abuse/Bench/Redis/TimeLimitBench.php +++ b/tests/Abuse/Bench/Redis/TimeLimitBench.php @@ -4,7 +4,7 @@ use Redis as Client; use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\Redis\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimit; use Utopia\Tests\Bench\Base; final class TimeLimitBench extends Base From 2663547e4216d423ad226c1b2f6680d02e936d9b Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 11:36:01 +0530 Subject: [PATCH 280/470] fix: tests --- docker-compose.yml | 9 +++++++ tests/Abuse/Base.php | 4 ++-- .../TimeLimitBench.php => Database.php} | 4 ++-- .../{Redis/TimeLimitBench.php => Redis.php} | 4 ++-- tests/Abuse/Bench/RedisCluster.php | 24 +++++++++++++++++++ tests/Abuse/DatabaseTest.php | 3 +-- tests/Abuse/RedisClusterTest.php | 3 +-- tests/Abuse/RedisTest.php | 3 +-- 8 files changed, 42 insertions(+), 12 deletions(-) rename tests/Abuse/Bench/{Database/TimeLimitBench.php => Database.php} (94%) rename tests/Abuse/Bench/{Redis/TimeLimitBench.php => Redis.php} (87%) create mode 100644 tests/Abuse/Bench/RedisCluster.php diff --git a/docker-compose.yml b/docker-compose.yml index 0599ca8545b..a139da027b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,15 @@ services: ports: - "9307:3306" + adminer: + image: adminer + container_name: abuse-adminer + restart: always + ports: + - 9506:8080 + networks: + - abuse + redis: image: redis:6.0-alpine container_name: redis diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 1b88f411f61..2b7e291bed5 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -53,11 +53,11 @@ public function testIsValid(): void public function testCleanup(): void { - // Check that there is only one log + // Check that there are 3 logs $logs = $this->abuse->getLogs(0, 10); $this->assertEquals(3, \count($logs)); - sleep(5); + sleep(2); // Delete the log $status = $this->abuse->cleanup($this->getCleanupDateTime()); diff --git a/tests/Abuse/Bench/Database/TimeLimitBench.php b/tests/Abuse/Bench/Database.php similarity index 94% rename from tests/Abuse/Bench/Database/TimeLimitBench.php rename to tests/Abuse/Bench/Database.php index 0525c44d817..a24c9ed4a78 100644 --- a/tests/Abuse/Bench/Database/TimeLimitBench.php +++ b/tests/Abuse/Bench/Database.php @@ -1,6 +1,6 @@ redis = new Client(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); + $this->adapter = new RedisCluster('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $this->abuse = new Abuse($this->adapter); + } +} diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index f4a9252caa1..8ca37426bf6 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -45,7 +45,6 @@ public function setUp(): void $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); - $this->abuse->cleanup($this->getCleanupDateTime()); } public function getAdapter(string $key, int $limit, int $seconds): Adapter @@ -55,6 +54,6 @@ public function getAdapter(string $key, int $limit, int $seconds): Adapter public function getCleanupDateTime(): string { - return DateTime::addSeconds(new \DateTime(), -1); + return DateTime::format(new \DateTime()); } } diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php index 3d39c56667b..ba1a60da963 100644 --- a/tests/Abuse/RedisClusterTest.php +++ b/tests/Abuse/RedisClusterTest.php @@ -23,7 +23,6 @@ public function setUp(): void $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); - $this->abuse->cleanup($this->getCleanupDateTime()); } public function getAdapter(string $key, int $limit, int $seconds): Adapter @@ -33,7 +32,7 @@ public function getAdapter(string $key, int $limit, int $seconds): Adapter public function getCleanupDateTime(): string { - $interval = DateInterval::createFromDateString(1 . ' seconds'); + $interval = DateInterval::createFromDateString('now'); return strval((new \DateTime())->sub($interval)->getTimestamp()); } } diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 8423db86084..e0bf69858a4 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -24,7 +24,6 @@ public function setUp(): void $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); $adapter->setParam('{{ip}}', '127.0.0.1'); $this->abuse = new Abuse($adapter); - $this->abuse->cleanup($this->getCleanupDateTime()); } public function getAdapter(string $key, int $limit, int $seconds): Adapter @@ -34,7 +33,7 @@ public function getAdapter(string $key, int $limit, int $seconds): Adapter public function getCleanupDateTime(): string { - $interval = DateInterval::createFromDateString(1 . ' seconds'); + $interval = DateInterval::createFromDateString('now'); return strval((new \DateTime())->sub($interval)->getTimestamp()); } } From 627d1ba56451dd70ca26a1253c42e039b4bfc1a7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 11:38:34 +0530 Subject: [PATCH 281/470] fix: tests --- tests/Abuse/Bench/RedisCluster.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Abuse/Bench/RedisCluster.php b/tests/Abuse/Bench/RedisCluster.php index 5367557ae48..74b730e70f8 100644 --- a/tests/Abuse/Bench/RedisCluster.php +++ b/tests/Abuse/Bench/RedisCluster.php @@ -4,7 +4,6 @@ use RedisCluster as Client; use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as TimeLimit; use Utopia\Abuse\Adapters\TimeLimit\RedisCluster; use Utopia\Tests\Bench\Base; From b3e9735cb3dd9298ac93b0f2ec1377cfc6054a79 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 20:10:18 +0530 Subject: [PATCH 282/470] Revert "chore: update db library" --- composer.lock | 96 +++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/composer.lock b/composer.lock index 82030d2556c..055b1ecad73 100644 --- a/composer.lock +++ b/composer.lock @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.55.1", + "version": "0.55", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828" + "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6f44079999491feb0ef85686f6b2ac7ef54db828", - "reference": "6f44079999491feb0ef85686f6b2ac7ef54db828", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0ad2eb1c163196cd79253c59f1896c5b03d6ba91", + "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.55.1" + "source": "https://github.com/utopia-php/database/tree/0.55" }, - "time": "2024-10-07T18:52:26+00:00" + "time": "2024-09-18T04:00:12+00:00" }, { "name": "utopia-php/di", @@ -884,16 +884,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", "shasum": "" }, "require": { @@ -936,9 +936,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-09-15T16:40:33+00:00" }, { "name": "phar-io/manifest", @@ -1261,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.6", + "version": "1.12.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", "shasum": "" }, "require": { @@ -1315,7 +1315,7 @@ "type": "github" } ], - "time": "2024-10-06T15:03:59+00:00" + "time": "2024-09-09T08:10:35+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1638,16 +1638,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.21", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", - "reference": "de6abf3b6f8dd955fac3caad3af7a9504e8c2ffa", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { @@ -1662,7 +1662,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", + "phpunit/php-code-coverage": "^9.2.31", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.4", @@ -1721,7 +1721,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.21" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -1737,7 +1737,7 @@ "type": "tidelift" } ], - "time": "2024-09-19T10:50:18+00:00" + "time": "2024-07-10T11:45:39+00:00" }, { "name": "psr/cache", @@ -2920,16 +2920,16 @@ }, { "name": "symfony/console", - "version": "v7.1.5", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", "shasum": "" }, "require": { @@ -2993,7 +2993,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.5" + "source": "https://github.com/symfony/console/tree/v7.1.4" }, "funding": [ { @@ -3009,7 +3009,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3080,16 +3080,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.1.5", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a" + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/61fe0566189bf32e8cfee78335d8776f64a66f5a", - "reference": "61fe0566189bf32e8cfee78335d8776f64a66f5a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", "shasum": "" }, "require": { @@ -3126,7 +3126,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.1.5" + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" }, "funding": [ { @@ -3142,7 +3142,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T09:16:35+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/finder", @@ -3595,16 +3595,16 @@ }, { "name": "symfony/process", - "version": "v7.1.5", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5c03ee6369281177f07f7c68252a280beccba847" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", - "reference": "5c03ee6369281177f07f7c68252a280beccba847", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { @@ -3636,7 +3636,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.5" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -3652,7 +3652,7 @@ "type": "tidelift" } ], - "time": "2024-09-19T21:48:23+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/service-contracts", @@ -3739,16 +3739,16 @@ }, { "name": "symfony/string", - "version": "v7.1.5", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -3806,7 +3806,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.5" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -3822,7 +3822,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "theseer/tokenizer", From 9760fee979d6aa7fdabec7a465f00e977c953654 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 20:13:09 +0530 Subject: [PATCH 283/470] Revert "Chore: findOne now returns empty documents instead of false on miss" --- composer.json | 2 +- composer.lock | 144 +++++++++++----------- src/Abuse/Adapters/Database/TimeLimit.php | 4 +- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/composer.json b/composer.json index 79685c2ffaa..823856bc1b9 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.55.*" + "utopia-php/database": "0.54.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 055b1ecad73..8ab1de626eb 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": "4ae74a41329275b52e4722de01c60285", + "content-hash": "7601e6576cde00888812fb6e2e732dbe", "packages": [ { "name": "jean85/pretty-package-versions", @@ -136,20 +136,20 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.1" }, "type": "library", "extra": { @@ -196,7 +196,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -212,7 +212,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "utopia-php/cache", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.55", + "version": "0.54.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91" + "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0ad2eb1c163196cd79253c59f1896c5b03d6ba91", - "reference": "0ad2eb1c163196cd79253c59f1896c5b03d6ba91", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1e97fc8b212a8daf9b9a68244677ed34c9db143e", + "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e", "shasum": "" }, "require": { @@ -316,9 +316,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.55" + "source": "https://github.com/utopia-php/database/tree/0.54.0" }, - "time": "2024-09-18T04:00:12+00:00" + "time": "2024-09-05T16:00:42+00:00" }, { "name": "utopia-php/di", @@ -370,16 +370,16 @@ }, { "name": "utopia-php/framework", - "version": "1.0.2", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "fc63ec61c720190a5ea5bb484c615145850951e6" + "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/fc63ec61c720190a5ea5bb484c615145850951e6", - "reference": "fc63ec61c720190a5ea5bb484c615145850951e6", + "url": "https://api.github.com/repos/utopia-php/http/zipball/cc880ec41f7f163d4f9956fec26cc6be51b412cf", + "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf", "shasum": "" }, "require": { @@ -414,9 +414,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/1.0.2" + "source": "https://github.com/utopia-php/http/tree/1.0.0" }, - "time": "2024-09-10T09:04:19+00:00" + "time": "2024-09-05T15:38:08+00:00" }, { "name": "utopia-php/mongo", @@ -480,16 +480,16 @@ }, { "name": "utopia-php/servers", - "version": "0.1.1", + "version": "0.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/servers.git", - "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a" + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/servers/zipball/fd5c8d32778f265256c1936372a071b944f5ba8a", - "reference": "fd5c8d32778f265256c1936372a071b944f5ba8a", + "url": "https://api.github.com/repos/utopia-php/servers/zipball/7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", + "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", "shasum": "" }, "require": { @@ -527,9 +527,9 @@ ], "support": { "issues": "https://github.com/utopia-php/servers/issues", - "source": "https://github.com/utopia-php/servers/tree/0.1.1" + "source": "https://github.com/utopia-php/servers/tree/0.1.0" }, - "time": "2024-09-06T02:25:56+00:00" + "time": "2024-08-08T14:31:39+00:00" } ], "packages-dev": [ @@ -884,16 +884,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.2.0", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -936,9 +936,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-09-15T16:40:33+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -1261,16 +1261,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.3", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", - "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", "shasum": "" }, "require": { @@ -1315,7 +1315,7 @@ "type": "github" } ], - "time": "2024-09-09T08:10:35+00:00" + "time": "2024-09-03T19:55:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1843,16 +1843,16 @@ }, { "name": "psr/log", - "version": "3.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "reference": "79dff0b268932c640297f5208d6298f71855c03e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", "shasum": "" }, "require": { @@ -1887,9 +1887,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "source": "https://github.com/php-fig/log/tree/3.0.1" }, - "time": "2024-09-11T13:17:53+00:00" + "time": "2024-08-21T13:31:24+00:00" }, { "name": "sebastian/cli-parser", @@ -3277,20 +3277,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.1" }, "provide": { "ext-ctype": "*" @@ -3336,7 +3336,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -3352,24 +3352,24 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" @@ -3414,7 +3414,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -3430,24 +3430,24 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.1" }, "suggest": { "ext-intl": "For best performance" @@ -3495,7 +3495,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -3511,24 +3511,24 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.1" }, "provide": { "ext-mbstring": "*" @@ -3575,7 +3575,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -3591,7 +3591,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/process", @@ -3925,7 +3925,7 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "RC", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index 0f1e779295c..2527442cfb6 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -165,7 +165,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if ($data->isEmpty()) { + if ($data === false) { $data = [ '$permissions' => [], 'key' => $key, @@ -183,7 +183,7 @@ protected function hit(string $key, string $datetime): void Query::equal('time', [$datetime]), ]); - if (!$data->isEmpty()) { + if ($data !== false && $data instanceof Document) { $count = $data->getAttribute('count', 0); if (\is_numeric($count)) { $this->count = intval($count); From f0c49a64fd80bdf7e2f7f10ec727c7d466f3e2e7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 18 Dec 2024 20:16:53 +0530 Subject: [PATCH 284/470] Revert "Framework" --- composer.json | 2 +- composer.lock | 208 +++++------------- src/Abuse/Adapters/Database/TimeLimit.php | 77 +------ tests/Abuse/Base.php | 0 tests/Abuse/Bench/Database/TimeLimitBench.php | 2 +- tests/Abuse/DatabaseTest.php | 2 +- tests/Abuse/RedisTest.php | 2 +- 7 files changed, 61 insertions(+), 232 deletions(-) mode change 100755 => 100644 tests/Abuse/Base.php diff --git a/composer.json b/composer.json index 823856bc1b9..6283871b3a3 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.54.*" + "utopia-php/database": "0.53.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 8ab1de626eb..152575410fb 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": "7601e6576cde00888812fb6e2e732dbe", + "content-hash": "6522b9fb8652f6801bd7c5664430c672", "packages": [ { "name": "jean85/pretty-package-versions", @@ -266,16 +266,16 @@ }, { "name": "utopia-php/database", - "version": "0.54.0", + "version": "0.53.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e" + "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/1e97fc8b212a8daf9b9a68244677ed34c9db143e", - "reference": "1e97fc8b212a8daf9b9a68244677ed34c9db143e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2e116d544b14e8b2edfcb5b793d214136ff64f42", + "reference": "2e116d544b14e8b2edfcb5b793d214136ff64f42", "shasum": "" }, "require": { @@ -283,7 +283,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "1.0.*", + "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -294,7 +294,7 @@ "phpunit/phpunit": "9.6.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.19.*" + "utopia-php/cli": "0.14.*" }, "type": "library", "autoload": { @@ -316,84 +316,32 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.54.0" + "source": "https://github.com/utopia-php/database/tree/0.53.0" }, - "time": "2024-09-05T16:00:42+00:00" - }, - { - "name": "utopia-php/di", - "version": "0.1.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/di.git", - "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31", - "reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/", - "Tests\\E2E\\": "tests/e2e" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple and lite library for managing dependency injections", - "keywords": [ - "framework", - "http", - "php", - "upf" - ], - "support": { - "issues": "https://github.com/utopia-php/di/issues", - "source": "https://github.com/utopia-php/di/tree/0.1.0" - }, - "time": "2024-08-08T14:35:19+00:00" + "time": "2024-08-30T04:32:56+00:00" }, { "name": "utopia-php/framework", - "version": "1.0.0", + "version": "0.33.8", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf" + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/cc880ec41f7f163d4f9956fec26cc6be51b412cf", - "reference": "cc880ec41f7f163d4f9956fec26cc6be51b412cf", + "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5", + "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5", "shasum": "" }, "require": { - "ext-swoole": "*", - "php": ">=8.0", - "utopia-php/servers": "0.1.*" + "php": ">=8.0" }, "require-dev": { - "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" + "phpunit/phpunit": "^9.5.25" }, "type": "library", "autoload": { @@ -405,18 +353,17 @@ "license": [ "MIT" ], - "description": "A simple, light and advanced PHP HTTP framework", + "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", - "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/1.0.0" + "source": "https://github.com/utopia-php/http/tree/0.33.8" }, - "time": "2024-09-05T15:38:08+00:00" + "time": "2024-08-15T14:10:09+00:00" }, { "name": "utopia-php/mongo", @@ -477,74 +424,21 @@ "source": "https://github.com/utopia-php/mongo/tree/0.3.1" }, "time": "2023-09-01T17:25:28+00:00" - }, - { - "name": "utopia-php/servers", - "version": "0.1.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/servers.git", - "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/servers/zipball/7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", - "reference": "7d9e4f364fb1ab1889fb89ca96eb9946467cb09c", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "utopia-php/di": "0.1.*" - }, - "require-dev": { - "laravel/pint": "^0.2.3", - "phpstan/phpstan": "^1.8", - "phpunit/phpunit": "^9.5.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Servers\\": "src/Servers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Team Appwrite", - "email": "team@appwrite.io" - } - ], - "description": "A base library for building Utopia style servers.", - "keywords": [ - "framework", - "php", - "servers", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/servers/issues", - "source": "https://github.com/utopia-php/servers/tree/0.1.0" - }, - "time": "2024-08-08T14:31:39+00:00" } ], "packages-dev": [ { "name": "doctrine/annotations", - "version": "2.0.2", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", "shasum": "" }, "require": { @@ -556,10 +450,10 @@ "require-dev": { "doctrine/cache": "^2.0", "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.10.28", + "phpstan/phpstan": "^1.8.0", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6.4 || ^7", - "vimeo/psalm": "^4.30 || ^5.14" + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" }, "suggest": { "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" @@ -605,9 +499,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.2" + "source": "https://github.com/doctrine/annotations/tree/2.0.1" }, - "time": "2024-09-05T10:17:24+00:00" + "time": "2023-02-02T22:02:53+00:00" }, { "name": "doctrine/instantiator", @@ -1261,16 +1155,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2" + "reference": "384af967d35b2162f69526c7276acadce534d0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", - "reference": "d8ed7fffa66de1db0d2972267d8ed1d8fa0fe5a2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", + "reference": "384af967d35b2162f69526c7276acadce534d0e1", "shasum": "" }, "require": { @@ -1315,7 +1209,7 @@ "type": "github" } ], - "time": "2024-09-03T19:55:22+00:00" + "time": "2024-08-27T09:18:05+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2920,16 +2814,16 @@ }, { "name": "symfony/console", - "version": "v7.1.4", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", - "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { @@ -2993,7 +2887,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.4" + "source": "https://github.com/symfony/console/tree/v7.1.3" }, "funding": [ { @@ -3009,7 +2903,7 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:53+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3146,16 +3040,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.4", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", + "reference": "717c6329886f32dc65e27461f80f2a465412fdca", "shasum": "" }, "require": { @@ -3190,7 +3084,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.4" + "source": "https://github.com/symfony/finder/tree/v7.1.3" }, "funding": [ { @@ -3206,7 +3100,7 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/options-resolver", @@ -3739,16 +3633,16 @@ }, { "name": "symfony/string", - "version": "v7.1.4", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", - "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { @@ -3806,7 +3700,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.4" + "source": "https://github.com/symfony/string/tree/v7.1.3" }, "funding": [ { @@ -3822,7 +3716,7 @@ "type": "tidelift" } ], - "time": "2024-08-12T09:59:40+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "theseer/tokenizer", @@ -3925,7 +3819,7 @@ } ], "aliases": [], - "minimum-stability": "RC", + "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, diff --git a/src/Abuse/Adapters/Database/TimeLimit.php b/src/Abuse/Adapters/Database/TimeLimit.php index 2527442cfb6..0d408295edb 100644 --- a/src/Abuse/Adapters/Database/TimeLimit.php +++ b/src/Abuse/Adapters/Database/TimeLimit.php @@ -10,7 +10,8 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Http\Exception; +use Utopia\Database\Validator\Authorization; +use Utopia\Exception; class TimeLimit extends TimeLimitAdapter { @@ -127,7 +128,7 @@ protected function count(string $key, string $datetime): int } /** @var array $result */ - $result = $this->db->getAuthorization()->skip(function () use ($key, $datetime) { + $result = Authorization::skip(function () use ($key, $datetime) { return $this->db->find(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -159,7 +160,7 @@ protected function hit(string $key, string $datetime): void return; } - $this->db->getAuthorization()->skip(function () use ($datetime, $key) { + Authorization::skip(function () use ($datetime, $key) { $data = $this->db->findOne(TimeLimit::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$datetime]), @@ -216,7 +217,7 @@ protected function hit(string $key, string $datetime): void public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ - $results = $this->db->getAuthorization()->skip(function () use ($offset, $limit) { + $results = Authorization::skip(function () use ($offset, $limit) { $queries = []; $queries[] = Query::orderDesc(''); @@ -243,7 +244,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(string $datetime): bool { - $this->db->getAuthorization()->skip(function () use ($datetime) { + Authorization::skip(function () use ($datetime) { do { $documents = $this->db->find(TimeLimit::COLLECTION, [ Query::lessThan('time', $datetime), @@ -257,70 +258,4 @@ public function cleanup(string $datetime): bool return true; } - - /** - * Check - * - * Checks if number of counts is bigger or smaller than current limit. limit 0 is equal to unlimited - * - * @return bool - * - * @throws \Throwable - */ - public function check(): bool - { - if (0 == $this->limit) { - return false; - } - - $key = $this->parseKey(); - - if ($this->limit > $this->count($key, $this->time)) { - $this->hit($key, $this->time); - - return false; - } - - return true; - } - - /** - * Remaining - * - * Returns the number of current remaining counts - * - * @return int - * - * @throws \Exception - */ - public function remaining(): int - { - $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done - - return (0 > $left) ? 0 : $left; - } - - /** - * Limit - * - * Return the limit integer - * - * @return int - */ - public function limit(): int - { - return $this->limit; - } - - /** - * Time - * - * Return the Datetime - * - * @return string - */ - public function time(): string - { - return $this->time; - } } diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php old mode 100755 new mode 100644 diff --git a/tests/Abuse/Bench/Database/TimeLimitBench.php b/tests/Abuse/Bench/Database/TimeLimitBench.php index 8f2a5be2cb2..e2a110c6b7b 100644 --- a/tests/Abuse/Bench/Database/TimeLimitBench.php +++ b/tests/Abuse/Bench/Database/TimeLimitBench.php @@ -10,8 +10,8 @@ use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; +use Utopia\Exception; use Utopia\Tests\Bench\Base; -use Utopia\Http\Exception; class TimeLimitBench extends Base { diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index fc6c1ed1979..b3820e6aa4a 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -12,7 +12,7 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; use Utopia\Database\DateTime; -use Utopia\Http\Exception; +use Utopia\Exception; class DatabaseTest extends Base { diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 4ecd92dd50c..517ee947777 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -7,7 +7,7 @@ use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapter; use Utopia\Abuse\Adapters\Redis\TimeLimit; -use Utopia\Http\Exception; +use Utopia\Exception; class RedisTest extends Base { From 397a49d2f783e14c3922b186f11e1e79ef68284f Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 20 Dec 2024 19:14:35 +0530 Subject: [PATCH 285/470] chore: update tests and change timestamp type --- docker-compose.yml | 6 +- phpunit.xml | 2 +- src/Abuse/Adapter.php | 4 +- src/Abuse/Adapters/TimeLimit.php | 24 ++-- src/Abuse/Adapters/TimeLimit/Database.php | 41 +++--- src/Abuse/Adapters/TimeLimit/Redis.php | 26 ++-- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 22 +-- tests/Abuse/Base.php | 129 ++++++++++++------ tests/Abuse/DatabaseTest.php | 43 +++--- tests/Abuse/RedisClusterTest.php | 46 ++++--- tests/Abuse/RedisTest.php | 42 +++--- 11 files changed, 234 insertions(+), 151 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a139da027b5..f614d13765d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,8 @@ services: - REDIS_HOSTS=redis-cluster-0:6379 ports: - "8081:5540" + volumes: + - redisinsight:/data redis-cluster-0: image: docker.io/bitnami/redis-cluster:7.4 @@ -88,4 +90,6 @@ services: - abuse networks: - abuse: \ No newline at end of file + abuse: +volumes: + redisinsight: diff --git a/phpunit.xml b/phpunit.xml index 10d408caa08..ec39a7edeaf 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 4af27dc40a3..e96fa6f7de0 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -79,8 +79,8 @@ abstract public function getLogs(?int $offset = null, ?int $limit = 25): array; /** * Delete all logs older than $datetime * - * @param string $datetime + * @param int $timestamp * @return bool */ - abstract public function cleanup(string $datetime): bool; + abstract public function cleanup(int $timestamp): bool; } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index dcafee08353..d0bd655a7a9 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -18,9 +18,9 @@ abstract class TimeLimit extends Adapter protected ?int $count = null; /** - * @var string + * @var int */ - protected string $time; + protected int $timestamp; /** * Check @@ -28,14 +28,14 @@ abstract class TimeLimit extends Adapter * Checks if number of counts is bigger or smaller than current limit * * @param string $key - * @param string $datetime + * @param int $timestamp * @return int * * @throws \Exception */ - abstract protected function count(string $key, string $datetime): int; + abstract protected function count(string $key, int $timestamp): int; - abstract protected function hit(string $key, string $datetime): void; + abstract protected function hit(string $key, int $timestamp): void; /** * Check @@ -54,8 +54,8 @@ public function check(): bool $key = $this->parseKey(); - if ($this->limit > $this->count($key, $this->time)) { - $this->hit($key, $this->time); + if ($this->limit > $this->count($key, $this->timestamp)) { + $this->hit($key, $this->timestamp); return false; } @@ -74,7 +74,7 @@ public function check(): bool */ public function remaining(): int { - $left = $this->limit - ($this->count($this->parseKey(), $this->time) + 1); // Add one because we need to say how many left not how many done + $left = $this->limit - ($this->count($this->parseKey(), $this->timestamp) + 1); // Add one because we need to say how many left not how many done return (0 > $left) ? 0 : $left; } @@ -94,12 +94,12 @@ public function limit(): int /** * Time * - * Return the Datetime + * Return the timestamp * - * @return string + * @return int */ - public function time(): string + public function time(): int { - return $this->time; + return $this->timestamp; } } diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index a7ba5662c29..ae36f01cd0d 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -80,8 +80,8 @@ class Database extends TimeLimit public function __construct(string $key, int $limit, int $seconds, UtopiaDB $db) { $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); // todo: any good Idea without time()? - $this->time = DateTime::format((new \DateTime())->setTimestamp($time)); + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; $this->db = $db; } @@ -121,12 +121,12 @@ public function setup(): void * Checks if number of counts is bigger or smaller than current limit * * @param string $key - * @param string $datetime + * @param int $timestamp * @return int * * @throws \Exception */ - protected function count(string $key, string $datetime): int + protected function count(string $key, int $timestamp): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -136,11 +136,13 @@ protected function count(string $key, string $datetime): int return $this->count; } + $timestamp = $this->toDateTime($timestamp); + /** @var array $result */ - $result = Authorization::skip(function () use ($key, $datetime) { + $result = Authorization::skip(function () use ($key, $timestamp) { return $this->db->find(self::COLLECTION, [ Query::equal('key', [$key]), - Query::equal('time', [$datetime]), + Query::equal('time', [$timestamp]), ]); }); @@ -158,28 +160,29 @@ protected function count(string $key, string $datetime): int /** * @param string $key - * @param string $datetime + * @param int $timestamp * @return void * * @throws AuthorizationException|Structure|\Exception|\Throwable */ - protected function hit(string $key, string $datetime): void + protected function hit(string $key, int $timestamp): void { if (0 == $this->limit) { // No limit no point for counting return; } - Authorization::skip(function () use ($datetime, $key) { + $timestamp = $this->toDateTime($timestamp); + Authorization::skip(function () use ($timestamp, $key) { $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), - Query::equal('time', [$datetime]), + Query::equal('time', [$timestamp]), ]); if ($data->isEmpty()) { $data = [ '$permissions' => [], 'key' => $key, - 'time' => $datetime, + 'time' => $timestamp, 'count' => 1, '$collection' => self::COLLECTION, ]; @@ -190,7 +193,7 @@ protected function hit(string $key, string $datetime): void // Duplicate in case of race condition $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), - Query::equal('time', [$datetime]), + Query::equal('time', [$timestamp]), ]); if (!$data->isEmpty()) { @@ -246,17 +249,18 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array /** * Delete logs older than $timestamp seconds * - * @param string $datetime + * @param int $timestamp * @return bool * * @throws AuthorizationException|\Exception */ - public function cleanup(string $datetime): bool + public function cleanup(int $timestamp): bool { - Authorization::skip(function () use ($datetime) { + $timestamp = $this->toDateTime($timestamp); + Authorization::skip(function () use ($timestamp) { do { $documents = $this->db->find(self::COLLECTION, [ - Query::lessThan('time', $datetime), + Query::lessThan('time', $timestamp), ]); foreach ($documents as $document) { @@ -267,4 +271,9 @@ public function cleanup(string $datetime): bool return true; } + + protected function toDateTime(int $timestamp): string + { + return DateTime::format((new \DateTime())->setTimestamp($timestamp)); + } } diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 3657a570d5c..80a55726f29 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -17,8 +17,8 @@ public function __construct(string $key, int $limit, int $seconds, \Redis $redis { $this->redis = $redis; $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); - $this->time = strval($time); + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; } @@ -26,10 +26,10 @@ public function __construct(string $key, int $limit, int $seconds, \Redis $redis * Undocumented function * * @param string $key - * @param string $datetime + * @param int $timestamp * @return integer */ - protected function count(string $key, string $datetime): int + protected function count(string $key, int $timestamp): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -40,7 +40,7 @@ protected function count(string $key, string $datetime): int } /** @var string $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); if (!$count) { $this->count = 0; } else { @@ -52,25 +52,25 @@ protected function count(string $key, string $datetime): int /** * @param string $key - * @param string $datetime + * @param int $timestamp * @return void * */ - protected function hit(string $key, string $datetime): void + protected function hit(string $key, int $timestamp): void { if (0 == $this->limit) { // No limit no point for counting return; } /** @var string $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); if (!$count) { $this->count = 0; } else { $this->count = intval($count); } - $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $datetime); + $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $timestamp); $this->count++; } @@ -100,17 +100,17 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array } /** - * Delete all logs older than $datetime + * Delete all logs older than $timestamp * - * @param string $datetime + * @param int $timestamp * @return bool */ - public function cleanup(string $datetime): bool + public function cleanup(int $timestamp): bool { $iterator = null; while ($iterator !== 0) { $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 1000); - $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); + $keys = $this->filterKeys($keys ? $keys : [], $timestamp); $this->redis->del($keys); } return true; diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 24145d2b4ba..4416ac85342 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -17,8 +17,8 @@ public function __construct(string $key, int $limit, int $seconds, \RedisCluster { $this->redis = $redis; $this->key = $key; - $time = (int) \date('U', (int) (\floor(\time() / $seconds)) * $seconds); - $this->time = strval($time); + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; } @@ -29,7 +29,7 @@ public function __construct(string $key, int $limit, int $seconds, \RedisCluster * @param string $datetime * @return integer */ - protected function count(string $key, string $datetime): int + protected function count(string $key, int $timestamp): int { if (0 == $this->limit) { // No limit no point for counting return 0; @@ -40,7 +40,7 @@ protected function count(string $key, string $datetime): int } /** @var string|false $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); if ($count === false) { $this->count = 0; } else { @@ -57,21 +57,21 @@ protected function count(string $key, string $datetime): int * @param string $datetime * @return void */ - protected function hit(string $key, string $datetime): void + protected function hit(string $key, int $timestamp): void { if (0 == $this->limit) { // No limit no point for counting return; } /** @var string|false $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $datetime); + $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); if ($count === false) { $this->count = 0; } else { $this->count = intval($count); } - $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $datetime); + $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $timestamp); $this->count++; } @@ -100,15 +100,15 @@ public function getLogs(?int $offset = 0, ?int $limit = 25): array } /** - * Delete all logs older than $datetime + * Delete all logs older than $timestamp * - * @param string $datetime + * @param int $timestamp * @return bool */ - public function cleanup(string $datetime): bool + public function cleanup(int $timestamp): bool { $keys = $this->scan(self::NAMESPACE . '__*__*'); - $keys = $this->filterKeys($keys ? $keys : [], (int) $datetime); + $keys = $this->filterKeys($keys ? $keys : [], $timestamp); /** @phpstan-ignore-next-line */ $this->redis->del($keys); return true; diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 2b7e291bed5..a59b32d5f49 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -4,67 +4,116 @@ use PHPUnit\Framework\TestCase; use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapter; +use Utopia\Abuse\Adapters\TimeLimit; abstract class Base extends TestCase { - protected Abuse $abuse; + abstract public function getAdapter(string $key, int $limit, int $seconds): TimeLimit; - protected Abuse $abuseIp; - - abstract public function getAdapter(string $key, int $limit, int $seconds): Adapter; - - abstract public function getCleanupDateTime(): string; - - public function tearDown(): void + /** + * Test a static key with a limit of 2 requests per second + */ + public function testStaticKey(): void { - unset($this->abuse); + $adapter = $this->getAdapter('static-key', 2, 1); + $abuse = new Abuse($adapter); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), true); } - public function testImitate2Requests(): void + /** + * Test a dynamic key with a limit of 2 requests per second + */ + public function testDynamicKey(): void { - $key = '{{ip}}'; - $value = '0.0.0.10'; - - $adapter = $this->getAdapter($key, 1, 1); - $adapter->setParam($key, $value); - $this->abuseIp = new Abuse($adapter); - $this->assertEquals($this->abuseIp->check(), false); - $this->assertEquals($this->abuseIp->check(), true); - - sleep(1); + $adapter = $this->getAdapter('dyamic-key-{{ip}}', 2, 1); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $abuse = new Abuse($adapter); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), true); + } - $adapter = $this->getAdapter($key, 1, 1); - $adapter->setParam($key, $value); - $this->abuseIp = new Abuse($adapter); + /** + * Test a dynamic key with 2 params + */ + public function testDynamicKeyWith2Params(): void + { + $adapter = $this->getAdapter('two-params-{{ip}}-{{email}}', 2, 1); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $adapter->setParam('{{email}}', 'test@test.com'); + $abuse = new Abuse($adapter); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), false); + $this->assertEquals($abuse->check(), true); + } - $this->assertEquals($this->abuseIp->check(), false); - $this->assertEquals($this->abuseIp->check(), true); + /** + * Test a dynamic key with higher request rate like 100 requests per second + */ + public function testDynamicKeyFastRequests(): void + { + $adapter = $this->getAdapter('fast-requests-{{ip}}', 100, 1); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 100; $i++) { + $this->assertEquals($abuse->check(), false); + } + $this->assertEquals($abuse->check(), true); } - public function testIsValid(): void + /** + * Test that the limit is reset after the time limit + */ + public function testLimitReset(): void { - // Use vars to resolve adapter key - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), false); - $this->assertEquals($this->abuse->check(), true); + $adapter = $this->getAdapter('limit-reset-{{ip}}', 100, 2); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 100; $i++) { + $this->assertEquals($abuse->check(), false); + } + $this->assertEquals($abuse->check(), true); + + // Wait for the limit to reset + sleep(2); + + /** Seems to be a bug in the code where if use the same adapter, it caches the result of the previous check */ + $adapter = $this->getAdapter('limit-reset-{{ip}}', 100, 1); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $abuse = new Abuse($adapter); + $this->assertEquals($abuse->check(), false); } + /** + * Test logs are deleted after cleanup + */ public function testCleanup(): void { - // Check that there are 3 logs - $logs = $this->abuse->getLogs(0, 10); - $this->assertEquals(3, \count($logs)); + $adapter = $this->getAdapter('', 1, 1); + $abuse = new Abuse($adapter); - sleep(2); - // Delete the log + $logs = $abuse->getLogs(0, 100); + $this->assertEquals(6, \count($logs)); // 6 keys are created in the test - $status = $this->abuse->cleanup($this->getCleanupDateTime()); + sleep(1); + + $status = $abuse->cleanup(time()); $this->assertEquals($status, true); - // Check that there are no logs in the DB - $logs = $this->abuse->getLogs(0, 10); + $logs = $abuse->getLogs(0, 100); $this->assertEquals(0, \count($logs)); } + + /** + * Verify that the time format is correct + */ + public function testTimeFormat(): void + { + $now = time(); + $adapter = $this->getAdapter('', 1, 1); + $this->assertEquals($adapter->time(), $now); + $this->assertEquals(true, \is_int($adapter->time())); + } } diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index 8ca37426bf6..dd2be3e21c4 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -3,57 +3,56 @@ namespace Utopia\Tests; use PDO; -use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\Database as AdapterDatabase; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Database; -use Utopia\Database\DateTime; -use Utopia\Exception; class DatabaseTest extends Base { - protected Database $db; + protected static ?Database $db = null; - /** - * @throws Exception - * @throws \Exception - */ - public function setUp(): void + public static function setUpBeforeClass(): void + { + if (self::$db === null) { + self::$db = self::initialiseDatabase(); + } + } + + private static function initialiseDatabase(): Database { - // Limit login attempts to 3 time in 5 minutes time frame $dbHost = 'mysql'; $dbUser = 'root'; $dbPort = '3306'; $dbPass = 'password'; $pdo = new PDO("mysql:host={$dbHost};port={$dbPort};charset=utf8mb4", $dbUser, $dbPass, MariaDB::getPdoAttributes()); - $db = new Database(new MySQL($pdo), new Cache(new NoCache())); $db->setDatabase('utopiaTests'); $db->setNamespace('namespace'); - $this->db = $db; - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); - if (! $db->exists('utopiaTests')) { + $adapter = new AdapterDatabase('', 1, 1, $db); + if (!$db->exists('utopiaTests')) { $db->create(); $adapter->setup(); } - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); + return $db; } - public function getAdapter(string $key, int $limit, int $seconds): Adapter + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit { - return new TimeLimit($key, $limit, $seconds, $this->db); + return new AdapterDatabase($key, $limit, $seconds, self::$db); } - public function getCleanupDateTime(): string + public static function tearDownAfterClass(): void { - return DateTime::format(new \DateTime()); + if (self::$db !== null) { + self::$db->delete(); + self::$db = null; + } } } diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php index ba1a60da963..31fbed968a1 100644 --- a/tests/Abuse/RedisClusterTest.php +++ b/tests/Abuse/RedisClusterTest.php @@ -2,37 +2,51 @@ namespace Utopia\Tests; -use DateInterval; -use RedisCluster; -use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as AdapterRedisCluster; use Utopia\Exception; class RedisClusterTest extends Base { - protected RedisCluster $redis; + /** + * @var \RedisCluster|null + */ + protected static ?\RedisCluster $redis = null; /** * @throws Exception * @throws \Exception */ - public function setUp(): void + public static function setUpBeforeClass(): void { - $this->redis = new RedisCluster(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); + if (self::$redis === null) { + self::$redis = self::initialiseRedis(); + } } - public function getAdapter(string $key, int $limit, int $seconds): Adapter + private static function initialiseRedis(): \RedisCluster { - return new TimeLimit($key, $limit, $seconds, $this->redis); + return new \RedisCluster(null, [ + 'redis-cluster-0:6379', + 'redis-cluster-1:6379', + 'redis-cluster-2:6379', + 'redis-cluster-3:6379' + ]); } - public function getCleanupDateTime(): string + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit + { + return new AdapterRedisCluster($key, $limit, $seconds, self::$redis); + } + + /** + * Clean up Redis connection after all tests + */ + public static function tearDownAfterClass(): void { - $interval = DateInterval::createFromDateString('now'); - return strval((new \DateTime())->sub($interval)->getTimestamp()); + if (self::$redis !== null) { + self::$redis->close(); + self::$redis = null; + } } } diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index e0bf69858a4..643fdc50cf6 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -2,38 +2,46 @@ namespace Utopia\Tests; -use DateInterval; -use Redis as Client; -use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapter; -use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimit; +use Redis; +use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Abuse\Adapters\TimeLimit\Redis as AdapterRedis; use Utopia\Exception; class RedisTest extends Base { - protected Client $redis; + protected static ?\Redis $redis = null; /** * @throws Exception * @throws \Exception */ - public function setUp(): void + public static function setUpBeforeClass(): void { - $this->redis = new Client(); - $this->redis->connect('redis', 6379); - $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 1, $this->redis); - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); + if (self::$redis === null) { + self::$redis = self::initialiseRedis(); + } } - public function getAdapter(string $key, int $limit, int $seconds): Adapter + private static function initialiseRedis(): \Redis { - return new TimeLimit($key, $limit, $seconds, $this->redis); + $redis = new \Redis(); + $redis->connect('redis', 6379); + return $redis; } - public function getCleanupDateTime(): string + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit { - $interval = DateInterval::createFromDateString('now'); - return strval((new \DateTime())->sub($interval)->getTimestamp()); + return new AdapterRedis($key, $limit, $seconds, self::$redis); + } + + /** + * Clean up Redis connection after all tests + */ + public static function tearDownAfterClass(): void + { + if (self::$redis !== null) { + self::$redis->close(); + self::$redis = null; + } } } From d3119238ea05edd59f93ec99fb11545070e86eff Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 20 Dec 2024 19:41:20 +0530 Subject: [PATCH 286/470] chore: codeql and linter --- src/Abuse/Abuse.php | 8 ++++---- src/Abuse/Adapters/ReCaptcha.php | 6 +++--- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 8 ++++---- tests/Abuse/DatabaseTest.php | 11 ++++++----- tests/Abuse/RedisClusterTest.php | 14 ++++++-------- tests/Abuse/RedisTest.php | 11 ++++++----- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 7386c8a4faf..948939913d3 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -44,13 +44,13 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array } /** - * Delete all logs older than $datetime + * Delete all logs older than $timestamp * - * @param string $datetime + * @param int $timestamp * @return bool */ - public function cleanup(string $datetime): bool + public function cleanup(int $timestamp): bool { - return $this->adapter->cleanup($datetime); + return $this->adapter->cleanup($timestamp); } } diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index ae117ac5b93..15ecbdda84e 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -90,14 +90,14 @@ public function check(float $score = 0.5): bool } /** - * Delete logs older than $datetime + * Delete logs older than $timestamp * - * @param string $datetime + * @param int $timestamp * @return bool * * @throws Exception */ - public function cleanup(string $datetime): bool + public function cleanup(int $timestamp): bool { throw new Exception('Method not supported'); } diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 4416ac85342..80bd0e519aa 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -23,10 +23,10 @@ public function __construct(string $key, int $limit, int $seconds, \RedisCluster } /** - * Get count for a key at specific datetime + * Get count for a key at specific timestamp * * @param string $key - * @param string $datetime + * @param int $timestamp * @return integer */ protected function count(string $key, int $timestamp): int @@ -51,10 +51,10 @@ protected function count(string $key, int $timestamp): int } /** - * Record a hit for a key at specific datetime + * Record a hit for a key at specific timestamp * * @param string $key - * @param string $datetime + * @param int $timestamp * @return void */ protected function hit(string $key, int $timestamp): void diff --git a/tests/Abuse/DatabaseTest.php b/tests/Abuse/DatabaseTest.php index dd2be3e21c4..58356b2fe12 100755 --- a/tests/Abuse/DatabaseTest.php +++ b/tests/Abuse/DatabaseTest.php @@ -13,13 +13,15 @@ class DatabaseTest extends Base { - protected static ?Database $db = null; + protected static Database $db; public static function setUpBeforeClass(): void { - if (self::$db === null) { - self::$db = self::initialiseDatabase(); + if (isset(self::$db)) { + return; } + + self::$db = self::initialiseDatabase(); } private static function initialiseDatabase(): Database @@ -50,9 +52,8 @@ public function getAdapter(string $key, int $limit, int $seconds): TimeLimit public static function tearDownAfterClass(): void { - if (self::$db !== null) { + if (isset(self::$db)) { self::$db->delete(); - self::$db = null; } } } diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php index 31fbed968a1..e1029f946af 100644 --- a/tests/Abuse/RedisClusterTest.php +++ b/tests/Abuse/RedisClusterTest.php @@ -8,10 +8,7 @@ class RedisClusterTest extends Base { - /** - * @var \RedisCluster|null - */ - protected static ?\RedisCluster $redis = null; + protected static \RedisCluster $redis; /** * @throws Exception @@ -19,9 +16,11 @@ class RedisClusterTest extends Base */ public static function setUpBeforeClass(): void { - if (self::$redis === null) { - self::$redis = self::initialiseRedis(); + if (isset(self::$redis)) { + return; } + + self::$redis = self::initialiseRedis(); } private static function initialiseRedis(): \RedisCluster @@ -44,9 +43,8 @@ public function getAdapter(string $key, int $limit, int $seconds): TimeLimit */ public static function tearDownAfterClass(): void { - if (self::$redis !== null) { + if (isset(self::$redis)) { self::$redis->close(); - self::$redis = null; } } } diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 643fdc50cf6..085da1e4faf 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -9,7 +9,7 @@ class RedisTest extends Base { - protected static ?\Redis $redis = null; + protected static \Redis $redis; /** * @throws Exception @@ -17,9 +17,11 @@ class RedisTest extends Base */ public static function setUpBeforeClass(): void { - if (self::$redis === null) { - self::$redis = self::initialiseRedis(); + if (isset(self::$redis)) { + return; } + + self::$redis = self::initialiseRedis(); } private static function initialiseRedis(): \Redis @@ -39,9 +41,8 @@ public function getAdapter(string $key, int $limit, int $seconds): TimeLimit */ public static function tearDownAfterClass(): void { - if (self::$redis !== null) { + if (isset(self::$redis)) { self::$redis->close(); - self::$redis = null; } } } From 82f77e2875a58eca55ff5edbf4ae3083ceaf5f9b Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Fri, 20 Dec 2024 21:47:50 +0530 Subject: [PATCH 287/470] chore: review comment --- tests/Abuse/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index a59b32d5f49..a1d008fffe9 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -27,7 +27,7 @@ public function testStaticKey(): void */ public function testDynamicKey(): void { - $adapter = $this->getAdapter('dyamic-key-{{ip}}', 2, 1); + $adapter = $this->getAdapter('dynamic-key-{{ip}}', 2, 1); $adapter->setParam('{{ip}}', '0.0.0.10'); $abuse = new Abuse($adapter); $this->assertEquals($abuse->check(), false); From d985da2a80b129caf901ecfd597b0db3827439b1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:24:45 +0000 Subject: [PATCH 288/470] chore: optimise abuse --- src/Abuse/Adapters/TimeLimit/Redis.php | 73 +++++++++----------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 80a55726f29..d4b27e4d420 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -12,11 +12,17 @@ class Redis extends TimeLimit * @var \Redis */ protected \Redis $redis; + + /** + * @var int + */ + protected int $ttl; public function __construct(string $key, int $limit, int $seconds, \Redis $redis) { $this->redis = $redis; $this->key = $key; + $this->ttl = $seconds; $now = \time(); $this->timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; @@ -62,16 +68,13 @@ protected function hit(string $key, int $timestamp): void return; } - /** @var string $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); - if (!$count) { - $this->count = 0; - } else { - $this->count = intval($count); - } - - $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $timestamp); - $this->count++; + $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; + $this->redis->multi() + ->incr($key) + ->expire($key, $this->ttl) + ->exec(); + + $this->count = ($this->count ?? 0) + 1; } /** @@ -85,18 +88,19 @@ protected function hit(string $key, int $timestamp): void */ public function getLogs(?int $offset = null, ?int $limit = 25): array { - // TODO limit potential is SCAN but needs cursor no offset - $cursor = null; - $keys = $this->redis->scan($cursor, self::NAMESPACE . '__*', $limit); - if (!$keys) { - return []; - } + $cursor = 0; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + do { + [$cursor, $keys] = $this->redis->scan($cursor, $pattern, $limit); + if (!empty($keys)) { + $values = $this->redis->mget($keys); + $matches = array_merge($matches, array_combine($keys, $values)); + } + } while ($cursor > 0 && (!$limit || count($matches) < $limit)); - $logs = []; - foreach ($keys as $key) { - $logs[$key] = $this->redis->get($key); - } - return $logs; + return $matches; } /** @@ -107,32 +111,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array */ public function cleanup(int $timestamp): bool { - $iterator = null; - while ($iterator !== 0) { - $keys = $this->redis->scan($iterator, self::NAMESPACE . '__*__*', 1000); - $keys = $this->filterKeys($keys ? $keys : [], $timestamp); - $this->redis->del($keys); - } + // No need for manual cleanup - Redis TTL handles this automatically return true; } - - /** - * Filter keys - * - * @param array $keys - * @param integer $timestamp - * @return array - */ - protected function filterKeys(array $keys, int $timestamp): array - { - $filteredKeys = []; - foreach ($keys as $key) { - $parts = explode('__', $key); - $keyTimestamp = (int)end($parts); // Assuming the last part is always the timestamp - if ($keyTimestamp < $timestamp) { - $filteredKeys[] = $key; - } - } - return $filteredKeys; - } } From a9c7993286bf688b91ef84a2a101282da3b41d41 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:35:54 +0000 Subject: [PATCH 289/470] fix: check --- src/Abuse/Adapters/TimeLimit/Redis.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index d4b27e4d420..3b93684f9d7 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -84,7 +84,7 @@ protected function hit(string $key, int $timestamp): void * * @param int|null $offset * @param int|null $limit - * @return array + * @return array */ public function getLogs(?int $offset = null, ?int $limit = 25): array { @@ -93,10 +93,26 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $pattern = self::NAMESPACE . '__*'; do { - [$cursor, $keys] = $this->redis->scan($cursor, $pattern, $limit); + /** @var array{0: string|false, 1: array} */ + $result = $this->redis->scan($cursor, $pattern, $limit); + + if ($result === false) { + break; + } + + [$newCursor, $keys] = $result; + $cursor = (int)$newCursor; + if (!empty($keys)) { + /** @var array */ $values = $this->redis->mget($keys); - $matches = array_merge($matches, array_combine($keys, $values)); + if ($values !== false) { + /** @var array */ + $combinedArray = array_combine($keys, $values); + if ($combinedArray !== false) { + $matches = array_merge($matches, $combinedArray); + } + } } } while ($cursor > 0 && (!$limit || count($matches) < $limit)); From 79c8ce63ec244207762ee1ee9bd6d0ff39ddea52 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:36:05 +0000 Subject: [PATCH 290/470] chore: fmt --- src/Abuse/Adapters/TimeLimit/Redis.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 3b93684f9d7..d0afa8ba707 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -12,7 +12,7 @@ class Redis extends TimeLimit * @var \Redis */ protected \Redis $redis; - + /** * @var int */ @@ -73,7 +73,7 @@ protected function hit(string $key, int $timestamp): void ->incr($key) ->expire($key, $this->ttl) ->exec(); - + $this->count = ($this->count ?? 0) + 1; } @@ -91,18 +91,18 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $cursor = 0; $matches = []; $pattern = self::NAMESPACE . '__*'; - + do { /** @var array{0: string|false, 1: array} */ $result = $this->redis->scan($cursor, $pattern, $limit); - + if ($result === false) { break; } - + [$newCursor, $keys] = $result; $cursor = (int)$newCursor; - + if (!empty($keys)) { /** @var array */ $values = $this->redis->mget($keys); From cf97de1722ed95cceef426c76000a6204ec5d11e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:37:05 +0000 Subject: [PATCH 291/470] chore: remove obselete version --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f614d13765d..17566973868 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: mysql: image: mysql:8 From 03cf65cbe360fa8f6c760838af2d4f577294cbea Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:42:43 +0000 Subject: [PATCH 292/470] feat: redis cluster --- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 112 +++++++----------- 1 file changed, 42 insertions(+), 70 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 80bd0e519aa..47fa140b2e6 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -13,10 +13,16 @@ class RedisCluster extends TimeLimit */ protected \RedisCluster $redis; + /** + * @var int + */ + protected int $ttl; + public function __construct(string $key, int $limit, int $seconds, \RedisCluster $redis) { $this->redis = $redis; $this->key = $key; + $this->ttl = $seconds; $now = \time(); $this->timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; @@ -63,22 +69,18 @@ protected function hit(string $key, int $timestamp): void return; } - /** @var string|false $count */ - $count = $this->redis->get(self::NAMESPACE . '__'. $key .'__'. $timestamp); - if ($count === false) { - $this->count = 0; - } else { - $this->count = intval($count); - } + $key = self::NAMESPACE . '__'. $key .'__'. $timestamp; + + $this->redis->multi(\RedisCluster::MULTI); + $this->redis->incr($key); + $this->redis->expire($key, $this->ttl); + $this->redis->exec(); - $this->redis->incr(self::NAMESPACE . '__'. $key .'__'. $timestamp); - $this->count++; + $this->count = ($this->count ?? 0) + 1; } /** - * Get abuse logs - * - * Return logs with an offset and limit + * Get abuse logs with proper cursor-based pagination * * @param int|null $offset * @param int|null $limit @@ -86,76 +88,46 @@ protected function hit(string $key, int $timestamp): void */ public function getLogs(?int $offset = 0, ?int $limit = 25): array { - // TODO limit potential is SCAN but needs cursor no offset - $keys = $this->scan(self::NAMESPACE . '__*', $offset, $limit); - if ($keys === false) { - return []; + $offset = $offset ?? 0; + $limit = $limit ?? 25; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + // Get all keys from each master + foreach ($this->redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $this->redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + $matches = array_merge($matches, $keys); + } + } while ($cursor > 0 && count($matches) < $offset + $limit); } - $logs = []; - foreach ($keys as $key) { - $logs[$key] = $this->redis->get($key); + // Sort to ensure consistent ordering + sort($matches); + + // Apply offset and limit + $matches = array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; } - return $logs; + + // Batch fetch values using mget + $values = $this->redis->mget($matches); + return array_combine($matches, $values); } /** - * Delete all logs older than $timestamp + * No need for manual cleanup - using Redis TTL * * @param int $timestamp * @return bool */ public function cleanup(int $timestamp): bool { - $keys = $this->scan(self::NAMESPACE . '__*__*'); - $keys = $this->filterKeys($keys ? $keys : [], $timestamp); - /** @phpstan-ignore-next-line */ - $this->redis->del($keys); return true; } - - /** - * Filter keys - * - * @param array $keys - * @param integer $timestamp - * @return array - */ - protected function filterKeys(array $keys, int $timestamp): array - { - $filteredKeys = []; - foreach ($keys as $key) { - $parts = explode('__', $key); - $keyTimestamp = (int)end($parts); // Assuming the last part is always the timestamp - if ($keyTimestamp < $timestamp) { - $filteredKeys[] = $key; - } - } - return $filteredKeys; - } - - /** - * Scan keys across all masters in the Redis cluster - * - * @param string $pattern Pattern to match keys - * @param int|null $cursor Reference to the cursor for scanning - * @param int|null $count Number of keys to return per iteration - * @return array|false - */ - protected function scan(string $pattern, ?int &$cursor = null, ?int $count = 1000): array|false - { - $matches = []; - foreach ($this->redis->_masters() as $master) { - $cursor = null; - do { - /** @phpstan-ignore-next-line */ - $keys = $this->redis->scan($cursor, $master, $pattern, $count); - if ($keys !== false) { - $matches = array_merge($matches, $keys); - } - } while ($cursor > 0); - } - - return empty($matches) ? false : $matches; - } } From 7f2b6b032cf7e83613af29bad60a3ce03e39e1a9 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:54:53 +0000 Subject: [PATCH 293/470] fix: redis scan --- src/Abuse/Adapters/TimeLimit/Redis.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index d0afa8ba707..fc4cb8fc649 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -93,20 +93,19 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $pattern = self::NAMESPACE . '__*'; do { - /** @var array{0: string|false, 1: array} */ $result = $this->redis->scan($cursor, $pattern, $limit); - - if ($result === false) { - break; + + // Early return if scan failed + if (!is_array($result)) { + return []; } [$newCursor, $keys] = $result; $cursor = (int)$newCursor; if (!empty($keys)) { - /** @var array */ $values = $this->redis->mget($keys); - if ($values !== false) { + if (is_array($values)) { /** @var array */ $combinedArray = array_combine($keys, $values); if ($combinedArray !== false) { From e7c208fa993741e7cce90b40ca6b96cc3356237c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:03:46 +0000 Subject: [PATCH 294/470] chore: revert redis --- src/Abuse/Adapters/TimeLimit/Redis.php | 42 ++++++++------------------ 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index fc4cb8fc649..19602613a2f 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -84,38 +84,22 @@ protected function hit(string $key, int $timestamp): void * * @param int|null $offset * @param int|null $limit - * @return array + * @return array */ public function getLogs(?int $offset = null, ?int $limit = 25): array { - $cursor = 0; - $matches = []; - $pattern = self::NAMESPACE . '__*'; - - do { - $result = $this->redis->scan($cursor, $pattern, $limit); - - // Early return if scan failed - if (!is_array($result)) { - return []; - } - - [$newCursor, $keys] = $result; - $cursor = (int)$newCursor; - - if (!empty($keys)) { - $values = $this->redis->mget($keys); - if (is_array($values)) { - /** @var array */ - $combinedArray = array_combine($keys, $values); - if ($combinedArray !== false) { - $matches = array_merge($matches, $combinedArray); - } - } - } - } while ($cursor > 0 && (!$limit || count($matches) < $limit)); - - return $matches; + // TODO limit potential is SCAN but needs cursor no offset + $cursor = null; + $keys = $this->redis->scan($cursor, self::NAMESPACE . '__*', $limit); + if (!$keys) { + return []; + } + + $logs = []; + foreach ($keys as $key) { + $logs[$key] = $this->redis->get($key); + } + return $logs; } /** From b63b78274506e617ce6e2f93f2bcddbd970014eb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:15:56 +0000 Subject: [PATCH 295/470] chore: fmt --- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 47fa140b2e6..72c297edbf2 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -70,7 +70,7 @@ protected function hit(string $key, int $timestamp): void } $key = self::NAMESPACE . '__'. $key .'__'. $timestamp; - + $this->redis->multi(\RedisCluster::MULTI); $this->redis->incr($key); $this->redis->expire($key, $this->ttl); From d7b8cf59966511fc7181f5ed26e50b780e93430b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:17:16 +0000 Subject: [PATCH 296/470] fix: tests --- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 72c297edbf2..6acc99a8f41 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -71,7 +71,7 @@ protected function hit(string $key, int $timestamp): void $key = self::NAMESPACE . '__'. $key .'__'. $timestamp; - $this->redis->multi(\RedisCluster::MULTI); + $this->redis->multi(); $this->redis->incr($key); $this->redis->expire($key, $this->ttl); $this->redis->exec(); From 91091d66e97fa02c25f02ae35f286937b87ed8cf Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:27:21 +0000 Subject: [PATCH 297/470] chore: remove test cleanup --- tests/Abuse/Base.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index a1d008fffe9..5cb59c7da77 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -86,26 +86,6 @@ public function testLimitReset(): void $this->assertEquals($abuse->check(), false); } - /** - * Test logs are deleted after cleanup - */ - public function testCleanup(): void - { - $adapter = $this->getAdapter('', 1, 1); - $abuse = new Abuse($adapter); - - $logs = $abuse->getLogs(0, 100); - $this->assertEquals(6, \count($logs)); // 6 keys are created in the test - - sleep(1); - - $status = $abuse->cleanup(time()); - $this->assertEquals($status, true); - - $logs = $abuse->getLogs(0, 100); - $this->assertEquals(0, \count($logs)); - } - /** * Verify that the time format is correct */ From a59fe2a949e499db56647e28e01511ab0ef3e9bd Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:37:59 +0000 Subject: [PATCH 298/470] chore: fix benchmarking --- composer.lock | 12 ++++++------ phpbench.json | 10 ++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/composer.lock b/composer.lock index 0ec24c8c21c..e9343924bd4 100644 --- a/composer.lock +++ b/composer.lock @@ -149,16 +149,16 @@ }, { "name": "google/protobuf", - "version": "v4.29.1", + "version": "v4.29.2", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "6042b5483f8029e42473faeb8ef75ba266278381" + "reference": "79aa5014efeeec3d137df5cdb0ae2fc163953945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", - "reference": "6042b5483f8029e42473faeb8ef75ba266278381", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/79aa5014efeeec3d137df5cdb0ae2fc163953945", + "reference": "79aa5014efeeec3d137df5cdb0ae2fc163953945", "shasum": "" }, "require": { @@ -187,9 +187,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.2" }, - "time": "2024-12-03T22:07:45+00:00" + "time": "2024-12-18T14:11:12+00:00" }, { "name": "jean85/pretty-package-versions", diff --git a/phpbench.json b/phpbench.json index adc40d12944..45395c8d87f 100644 --- a/phpbench.json +++ b/phpbench.json @@ -1,6 +1,8 @@ { - "$schema":"vendor/phpbench/phpbench/phpbench.schema.json", - "runner.bootstrap": "vendor/autoload.php", - "runner.path": "tests", - "runner.file_pattern": "*Bench.php" + "$schema": "vendor/phpbench/phpbench/phpbench.schema.json", + "runner": { + "bootstrap": "vendor/autoload.php", + "path": "tests/Abuse/Bench", + "file_pattern": "*.php" + } } \ No newline at end of file From 440ebd62c35b79f176d4ec5674f91a6d5528d6e3 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:39:32 +0000 Subject: [PATCH 299/470] fix: bench --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 5edc270736c..cc801b6c312 100755 --- a/composer.json +++ b/composer.json @@ -29,5 +29,11 @@ "phpstan/phpstan": "^1.9", "laravel/pint": "1.5.*", "phpbench/phpbench": "^1.2" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "tbachert/spi": true + } } } From 072725daa471ae53eb7483d06ed9689632525e0c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:44:21 +0000 Subject: [PATCH 300/470] chore: bench --- phpbench.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/phpbench.json b/phpbench.json index 45395c8d87f..19e8aa05945 100644 --- a/phpbench.json +++ b/phpbench.json @@ -1,8 +1,6 @@ { "$schema": "vendor/phpbench/phpbench/phpbench.schema.json", - "runner": { - "bootstrap": "vendor/autoload.php", - "path": "tests/Abuse/Bench", - "file_pattern": "*.php" - } + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Abuse/Bench", + "runner.file_pattern": "*.php" } \ No newline at end of file From 8c950cc3d09cf0d1824f86d1cbcf8e064aaeaceb Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 2 Jan 2025 14:26:25 +0200 Subject: [PATCH 301/470] bumb database version --- composer.json | 2 +- composer.lock | 80 +++++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index cc801b6c312..731ef5c0b2b 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.53.200" + "utopia-php/database": "0.53.29" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index e9343924bd4..0e33d8a2bf6 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": "99ca13df0ed5cb02c170e711cc847511", + "content-hash": "c8191f8fc4877e68521bc8d2f97394c7", "packages": [ { "name": "brick/math", @@ -1408,12 +1408,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -1458,16 +1458,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.1", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" + "reference": "339ba21476eb184290361542f732ad12c97591ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", + "reference": "339ba21476eb184290361542f732ad12c97591ec", "shasum": "" }, "require": { @@ -1533,7 +1533,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.1" + "source": "https://github.com/symfony/http-client/tree/v7.2.2" }, "funding": [ { @@ -1549,7 +1549,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2024-12-30T18:35:15+00:00" }, { "name": "symfony/http-client-contracts", @@ -1889,12 +1889,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.200", + "version": "0.53.29", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc" + "reference": "466ddedc0708c9faa09916d13608c117980602d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/570c63a3760d0e1404679ddfacd9484af40bd9fc", - "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc", + "url": "https://api.github.com/repos/utopia-php/database/zipball/466ddedc0708c9faa09916d13608c117980602d8", + "reference": "466ddedc0708c9faa09916d13608c117980602d8", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.200" + "source": "https://github.com/utopia-php/database/tree/0.53.29" }, - "time": "2024-12-01T07:59:15+00:00" + "time": "2025-01-02T10:57:56+00:00" }, { "name": "utopia-php/framework", @@ -2662,16 +2662,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -2714,9 +2714,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -3040,16 +3040,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.13", + "version": "1.12.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "9b469068840cfa031e1deaf2fa1886d00e20680f" + "reference": "e73868f809e68fff33be961ad4946e2e43ec9e38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b469068840cfa031e1deaf2fa1886d00e20680f", - "reference": "9b469068840cfa031e1deaf2fa1886d00e20680f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e73868f809e68fff33be961ad4946e2e43ec9e38", + "reference": "e73868f809e68fff33be961ad4946e2e43ec9e38", "shasum": "" }, "require": { @@ -3094,7 +3094,7 @@ "type": "github" } ], - "time": "2024-12-17T17:00:20+00:00" + "time": "2024-12-31T07:26:13+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4755,16 +4755,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -4799,7 +4799,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.0" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -4815,7 +4815,7 @@ "type": "tidelift" } ], - "time": "2024-10-23T06:56:12+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/options-resolver", @@ -5372,7 +5372,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5381,6 +5381,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } From c8a403bd79694c9790e3023e0a84c6e350ae0f2a Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Jan 2025 20:26:06 +1300 Subject: [PATCH 302/470] Update db --- composer.json | 2 +- composer.lock | 124 +++++++++++++++++++++++++------------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/composer.json b/composer.json index 5edc270736c..3559e0866ea 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.53.200" + "utopia-php/database": "0.53.31" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index f8481d3db80..3bd403699ea 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": "99ca13df0ed5cb02c170e711cc847511", + "content-hash": "ee4b9cdab07cb3ba558c4bfcb665aa5b", "packages": [ { "name": "brick/math", @@ -149,16 +149,16 @@ }, { "name": "google/protobuf", - "version": "v4.29.1", + "version": "v4.29.3", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "6042b5483f8029e42473faeb8ef75ba266278381" + "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/6042b5483f8029e42473faeb8ef75ba266278381", - "reference": "6042b5483f8029e42473faeb8ef75ba266278381", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", + "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", "shasum": "" }, "require": { @@ -187,9 +187,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.3" }, - "time": "2024-12-03T22:07:45+00:00" + "time": "2025-01-08T21:00:13+00:00" }, { "name": "jean85/pretty-package-versions", @@ -465,16 +465,16 @@ }, { "name": "open-telemetry/api", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed" + "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", - "reference": "04c85a1e41a3d59fa9bdc801a5de1df6624b95ed", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/351a30baa79699de3de3a814c8ccc7b52ccdfb1d", + "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d", "shasum": "" }, "require": { @@ -531,7 +531,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-11-16T04:32:30+00:00" + "time": "2025-01-08T23:50:34+00:00" }, { "name": "open-telemetry/context", @@ -594,16 +594,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18" + "reference": "243d9657c44a06f740cf384f486afe954c2b725f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/9b6de12204f25f8ab9540b46d6e7b5151897ce18", - "reference": "9b6de12204f25f8ab9540b46d6e7b5151897ce18", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/243d9657c44a06f740cf384f486afe954c2b725f", + "reference": "243d9657c44a06f740cf384f486afe954c2b725f", "shasum": "" }, "require": { @@ -654,7 +654,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-04-30T18:28:30+00:00" + "time": "2025-01-08T23:50:03+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -721,16 +721,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.1.2", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b" + "reference": "9a1c3b866239dbff291e5cc555bb7793eab08127" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/fb0ff8d8279a3776bd604791e2531dd0cc147e8b", - "reference": "fb0ff8d8279a3776bd604791e2531dd0cc147e8b", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/9a1c3b866239dbff291e5cc555bb7793eab08127", + "reference": "9a1c3b866239dbff291e5cc555bb7793eab08127", "shasum": "" }, "require": { @@ -758,13 +758,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - }, "spi": { "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] + }, + "branch-alias": { + "dev-main": "1.0.x-dev" } }, "autoload": { @@ -807,7 +807,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-10-18T21:01:35+00:00" + "time": "2025-01-08T23:50:34+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1408,12 +1408,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -1458,16 +1458,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.1", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" + "reference": "339ba21476eb184290361542f732ad12c97591ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", + "reference": "339ba21476eb184290361542f732ad12c97591ec", "shasum": "" }, "require": { @@ -1533,7 +1533,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.1" + "source": "https://github.com/symfony/http-client/tree/v7.2.2" }, "funding": [ { @@ -1549,7 +1549,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2024-12-30T18:35:15+00:00" }, { "name": "symfony/http-client-contracts", @@ -1889,12 +1889,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.200", + "version": "0.53.31", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc" + "reference": "874f2fa096c85a224fa1cdabea74760a7d7aac0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/570c63a3760d0e1404679ddfacd9484af40bd9fc", - "reference": "570c63a3760d0e1404679ddfacd9484af40bd9fc", + "url": "https://api.github.com/repos/utopia-php/database/zipball/874f2fa096c85a224fa1cdabea74760a7d7aac0c", + "reference": "874f2fa096c85a224fa1cdabea74760a7d7aac0c", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.200" + "source": "https://github.com/utopia-php/database/tree/0.53.31" }, - "time": "2024-12-01T07:59:15+00:00" + "time": "2025-01-10T06:51:16+00:00" }, { "name": "utopia-php/framework", @@ -2662,16 +2662,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -2714,9 +2714,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -3040,16 +3040,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.12", + "version": "1.12.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", - "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1", + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1", "shasum": "" }, "require": { @@ -3094,7 +3094,7 @@ "type": "github" } ], - "time": "2024-11-28T22:13:23+00:00" + "time": "2025-01-05T16:40:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4755,16 +4755,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -4799,7 +4799,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.0" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -4815,7 +4815,7 @@ "type": "tidelift" } ], - "time": "2024-10-23T06:56:12+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/options-resolver", From 2f5d9dcf96f41a1a1e1a2ede00b49531ac8cacd9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 10 Jan 2025 21:58:29 +1300 Subject: [PATCH 303/470] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 3559e0866ea..2d6cc7e3847 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.53.31" + "utopia-php/database": "0.53.32" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 3bd403699ea..9e097978fc7 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": "ee4b9cdab07cb3ba558c4bfcb665aa5b", + "content-hash": "982efa5227f29184fcf48b64e67ab632", "packages": [ { "name": "brick/math", @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.31", + "version": "0.53.32", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "874f2fa096c85a224fa1cdabea74760a7d7aac0c" + "reference": "981a1241139b42dccd531511130b79137740b205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/874f2fa096c85a224fa1cdabea74760a7d7aac0c", - "reference": "874f2fa096c85a224fa1cdabea74760a7d7aac0c", + "url": "https://api.github.com/repos/utopia-php/database/zipball/981a1241139b42dccd531511130b79137740b205", + "reference": "981a1241139b42dccd531511130b79137740b205", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.31" + "source": "https://github.com/utopia-php/database/tree/0.53.32" }, - "time": "2025-01-10T06:51:16+00:00" + "time": "2025-01-10T08:53:47+00:00" }, { "name": "utopia-php/framework", From 79a32e5935771c194ff71416b92540c1267a15ce Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 15 Jan 2025 15:36:19 +1300 Subject: [PATCH 304/470] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 055967504e0..c0b51e3713a 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.53.32" + "utopia-php/database": "0.56.*" }, "require-dev": { "phpunit/phpunit": "^9.4", diff --git a/composer.lock b/composer.lock index 9e097978fc7..da01dcefc3c 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": "982efa5227f29184fcf48b64e67ab632", + "content-hash": "232e9fd40caa96ac378ecf2ef588407f", "packages": [ { "name": "brick/math", @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.32", + "version": "0.56.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "981a1241139b42dccd531511130b79137740b205" + "reference": "e75b30bc31bb0afd5429355c175b670cd007d628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/981a1241139b42dccd531511130b79137740b205", - "reference": "981a1241139b42dccd531511130b79137740b205", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e75b30bc31bb0afd5429355c175b670cd007d628", + "reference": "e75b30bc31bb0afd5429355c175b670cd007d628", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.32" + "source": "https://github.com/utopia-php/database/tree/0.56.1" }, - "time": "2025-01-10T08:53:47+00:00" + "time": "2025-01-15T01:57:03+00:00" }, { "name": "utopia-php/framework", From 6f6680342b16b92e659d88f5a810d4a89dcf2ce9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 23 Jan 2025 17:30:29 +1300 Subject: [PATCH 305/470] Update database --- composer.json | 12 +++---- composer.lock | 84 +++++++++++++++++++++---------------------- src/Abuse/Adapter.php | 24 ++++++------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/composer.json b/composer.json index c0b51e3713a..752864f7b4f 100755 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "psr-4": {"Utopia\\Tests\\": "tests/Abuse"} }, "scripts": { - "check": "./vendor/bin/phpstan analyse --level max src tests", + "check": "./vendor/bin/phpstan analyse --level max --memory-limit=2G src tests", "lint": "./vendor/bin/pint --test", "format": "./vendor/bin/pint", "bench": "vendor/bin/phpbench run --report=aggregate" @@ -22,13 +22,13 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.56.*" + "utopia-php/database": "0.57.*" }, "require-dev": { - "phpunit/phpunit": "^9.4", - "phpstan/phpstan": "^1.9", - "laravel/pint": "1.5.*", - "phpbench/phpbench": "^1.2" + "phpunit/phpunit": "9.*", + "phpstan/phpstan": "1.*", + "laravel/pint": "1.*", + "phpbench/phpbench": "1.*" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index da01dcefc3c..440a2c033bc 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": "232e9fd40caa96ac378ecf2ef588407f", + "content-hash": "77a4ec3328d5afd067e26bc94ec924b3", "packages": [ { "name": "brick/math", @@ -658,16 +658,16 @@ }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.2.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d" + "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/66c3b98e998a726691c92e6405a82e6e7b8b169d", - "reference": "66c3b98e998a726691c92e6405a82e6e7b8b169d", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", + "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", "shasum": "" }, "require": { @@ -717,7 +717,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-10-30T11:49:49+00:00" + "time": "2025-01-15T23:07:07+00:00" }, { "name": "open-telemetry/sdk", @@ -2052,16 +2052,16 @@ }, { "name": "utopia-php/compression", - "version": "0.1.2", + "version": "0.1.3", "source": { "type": "git", "url": "https://github.com/utopia-php/compression.git", - "reference": "6062f70596415f8d5de40a589367b0eb2a435f98" + "reference": "66f093557ba66d98245e562036182016c7dcfe8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/compression/zipball/6062f70596415f8d5de40a589367b0eb2a435f98", - "reference": "6062f70596415f8d5de40a589367b0eb2a435f98", + "url": "https://api.github.com/repos/utopia-php/compression/zipball/66f093557ba66d98245e562036182016c7dcfe8a", + "reference": "66f093557ba66d98245e562036182016c7dcfe8a", "shasum": "" }, "require": { @@ -2092,22 +2092,22 @@ ], "support": { "issues": "https://github.com/utopia-php/compression/issues", - "source": "https://github.com/utopia-php/compression/tree/0.1.2" + "source": "https://github.com/utopia-php/compression/tree/0.1.3" }, - "time": "2024-11-08T14:59:54+00:00" + "time": "2025-01-15T15:15:51+00:00" }, { "name": "utopia-php/database", - "version": "0.56.1", + "version": "0.57.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e75b30bc31bb0afd5429355c175b670cd007d628" + "reference": "6ec27ade27e1b42b79ac42b06869e9c34d529357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e75b30bc31bb0afd5429355c175b670cd007d628", - "reference": "e75b30bc31bb0afd5429355c175b670cd007d628", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6ec27ade27e1b42b79ac42b06869e9c34d529357", + "reference": "6ec27ade27e1b42b79ac42b06869e9c34d529357", "shasum": "" }, "require": { @@ -2148,22 +2148,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.56.1" + "source": "https://github.com/utopia-php/database/tree/0.57.1" }, - "time": "2025-01-15T01:57:03+00:00" + "time": "2025-01-23T04:26:35+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.15", + "version": "0.33.16", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc" + "reference": "e91d4c560d1b809e25faa63d564fef034363b50f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/83b0628900c2c53e8c3efbf069f3e13050295edc", - "reference": "83b0628900c2c53e8c3efbf069f3e13050295edc", + "url": "https://api.github.com/repos/utopia-php/http/zipball/e91d4c560d1b809e25faa63d564fef034363b50f", + "reference": "e91d4c560d1b809e25faa63d564fef034363b50f", "shasum": "" }, "require": { @@ -2195,9 +2195,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.15" + "source": "https://github.com/utopia-php/http/tree/0.33.16" }, - "time": "2024-12-10T13:07:04+00:00" + "time": "2025-01-16T15:58:50+00:00" }, { "name": "utopia-php/mongo", @@ -2536,16 +2536,16 @@ }, { "name": "laravel/pint", - "version": "v1.5.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "e0a8cef58b74662f27355be9cdea0e726bbac362" + "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e0a8cef58b74662f27355be9cdea0e726bbac362", - "reference": "e0a8cef58b74662f27355be9cdea0e726bbac362", + "url": "https://api.github.com/repos/laravel/pint/zipball/53072e8ea22213a7ed168a8a15b96fbb8b82d44b", + "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b", "shasum": "" }, "require": { @@ -2553,16 +2553,16 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.0" + "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.14.4", - "illuminate/view": "^9.51.0", - "laravel-zero/framework": "^9.2.0", - "mockery/mockery": "^1.5.1", - "nunomaduro/larastan": "^2.4.0", - "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^1.22.4" + "friendsofphp/php-cs-fixer": "^3.66.0", + "illuminate/view": "^10.48.25", + "larastan/larastan": "^2.9.12", + "laravel-zero/framework": "^10.48.25", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^1.17.0", + "pestphp/pest": "^2.36.0" }, "bin": [ "builds/pint" @@ -2598,7 +2598,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-02-14T16:31:02+00:00" + "time": "2025-01-14T16:20:53+00:00" }, { "name": "myclabs/deep-copy", @@ -3040,16 +3040,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.15", + "version": "1.12.16", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1" + "reference": "e0bb5cb78545aae631220735aa706eac633a6be9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1", - "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9", + "reference": "e0bb5cb78545aae631220735aa706eac633a6be9", "shasum": "" }, "require": { @@ -3094,7 +3094,7 @@ "type": "github" } ], - "time": "2025-01-05T16:40:22+00:00" + "time": "2025-01-21T14:50:05+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index e96fa6f7de0..7de89678700 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -39,23 +39,23 @@ public function setParam(string $key, string $value): self return $this; } - /** - * Get Params - * - * Return array of all key params - * - * @return array - */ + /** + * Get Params + * + * Return array of all key params + * + * @return array + */ protected function getParams(): array { return $this->params; } - /** - * Parse key with all custom attached params - * - * @return string - */ + /** + * Parse key with all custom attached params + * + * @return string + */ protected function parseKey(): string { foreach ($this->getParams() as $key => $value) { From 1bd0a16e350c4cd81a2b3914664dec3368e7100c Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 4 Feb 2025 16:10:20 +0900 Subject: [PATCH 306/470] Update database to 0.58 --- composer.json | 2 +- composer.lock | 115 ++++++++++++++------------------------------------ 2 files changed, 32 insertions(+), 85 deletions(-) diff --git a/composer.json b/composer.json index 752864f7b4f..c03dd66c63d 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.57.*" + "utopia-php/database": "0.58.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 440a2c033bc..d8e42d3fd76 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": "77a4ec3328d5afd067e26bc94ec924b3", + "content-hash": "d53b0b7c0d06f904cd1dfcc8b55d5ca7", "packages": [ { "name": "brick/math", @@ -465,16 +465,16 @@ }, { "name": "open-telemetry/api", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d" + "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/351a30baa79699de3de3a814c8ccc7b52ccdfb1d", - "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/74b1a03263be8c5acb578f41da054b4bac3af4a0", + "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0", "shasum": "" }, "require": { @@ -531,7 +531,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-08T23:50:34+00:00" + "time": "2025-01-20T23:35:16+00:00" }, { "name": "open-telemetry/context", @@ -721,16 +721,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "9a1c3b866239dbff291e5cc555bb7793eab08127" + "reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/9a1c3b866239dbff291e5cc555bb7793eab08127", - "reference": "9a1c3b866239dbff291e5cc555bb7793eab08127", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1", + "reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1", "shasum": "" }, "require": { @@ -807,7 +807,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-08T23:50:34+00:00" + "time": "2025-01-09T23:17:14+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1458,16 +1458,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.2", + "version": "v7.2.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "339ba21476eb184290361542f732ad12c97591ec" + "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", - "reference": "339ba21476eb184290361542f732ad12c97591ec", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", "shasum": "" }, "require": { @@ -1533,7 +1533,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.2" + "source": "https://github.com/symfony/http-client/tree/v7.2.3" }, "funding": [ { @@ -1549,7 +1549,7 @@ "type": "tidelift" } ], - "time": "2024-12-30T18:35:15+00:00" + "time": "2025-01-28T15:51:35+00:00" }, { "name": "symfony/http-client-contracts", @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.57.1", + "version": "0.58.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6ec27ade27e1b42b79ac42b06869e9c34d529357" + "reference": "bc5b12752206a202fca1680cd9ccb4e7e86e3484" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6ec27ade27e1b42b79ac42b06869e9c34d529357", - "reference": "6ec27ade27e1b42b79ac42b06869e9c34d529357", + "url": "https://api.github.com/repos/utopia-php/database/zipball/bc5b12752206a202fca1680cd9ccb4e7e86e3484", + "reference": "bc5b12752206a202fca1680cd9ccb4e7e86e3484", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.57.1" + "source": "https://github.com/utopia-php/database/tree/0.58.1" }, - "time": "2025-01-23T04:26:35+00:00" + "time": "2025-02-04T05:46:41+00:00" }, { "name": "utopia-php/framework", @@ -2887,70 +2887,18 @@ }, "time": "2023-10-30T13:38:26+00:00" }, - { - "name": "phpbench/dom", - "version": "0.3.3", - "source": { - "type": "git", - "url": "https://github.com/phpbench/dom.git", - "reference": "786a96db538d0def931f5b19225233ec42ec7a72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpbench/dom/zipball/786a96db538d0def931f5b19225233ec42ec7a72", - "reference": "786a96db538d0def931f5b19225233ec42ec7a72", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": "^7.3||^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.14", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.0||^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpBench\\Dom\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Leech", - "email": "daniel@dantleech.com" - } - ], - "description": "DOM wrapper to simplify working with the PHP DOM implementation", - "support": { - "issues": "https://github.com/phpbench/dom/issues", - "source": "https://github.com/phpbench/dom/tree/0.3.3" - }, - "abandoned": true, - "time": "2023-03-06T23:46:57+00:00" - }, { "name": "phpbench/phpbench", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0" + "reference": "4248817222514421cba466bfa7adc7d8932345d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", - "reference": "a3e1ef08d9d7736d43a7fbd444893d6a073c0ca0", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4", + "reference": "4248817222514421cba466bfa7adc7d8932345d4", "shasum": "" }, "require": { @@ -2963,7 +2911,6 @@ "ext-tokenizer": "*", "php": "^8.1", "phpbench/container": "^2.2", - "phpbench/dom": "~0.3.3", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", "symfony/console": "^6.1 || ^7.0", @@ -2982,8 +2929,8 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.4", - "rector/rector": "^0.18.11 || ^1.0.0", + "phpunit/phpunit": "^10.4 || ^11.0", + "rector/rector": "^1.2", "symfony/error-handler": "^6.1 || ^7.0", "symfony/var-dumper": "^6.1 || ^7.0" }, @@ -3028,7 +2975,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.3.1" + "source": "https://github.com/phpbench/phpbench/tree/1.4.0" }, "funding": [ { @@ -3036,7 +2983,7 @@ "type": "github" } ], - "time": "2024-06-30T11:04:37+00:00" + "time": "2025-01-26T19:54:45+00:00" }, { "name": "phpstan/phpstan", From 6403c8e8f1dfaf3a2cd172d72505810c4f8bdd4e Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 4 Feb 2025 16:32:05 +0900 Subject: [PATCH 307/470] Update version to fix 8.1 --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index d8e42d3fd76..f663c3a211f 100644 --- a/composer.lock +++ b/composer.lock @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.58.1", + "version": "0.58.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "bc5b12752206a202fca1680cd9ccb4e7e86e3484" + "reference": "7934edaddb9d8aad3e6c43429adc19e7c6802d2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/bc5b12752206a202fca1680cd9ccb4e7e86e3484", - "reference": "bc5b12752206a202fca1680cd9ccb4e7e86e3484", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7934edaddb9d8aad3e6c43429adc19e7c6802d2a", + "reference": "7934edaddb9d8aad3e6c43429adc19e7c6802d2a", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.58.1" + "source": "https://github.com/utopia-php/database/tree/0.58.2" }, - "time": "2025-02-04T05:46:41+00:00" + "time": "2025-02-04T07:29:51+00:00" }, { "name": "utopia-php/framework", From f2f9cc0af5882378939944014a1bdfd59560a489 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 12 Feb 2025 22:08:22 +1300 Subject: [PATCH 308/470] Update database --- composer.json | 2 +- composer.lock | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index c03dd66c63d..fd6c52fcd98 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.58.*" + "utopia-php/database": "0.59.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index f663c3a211f..a764520f66c 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": "d53b0b7c0d06f904cd1dfcc8b55d5ca7", + "content-hash": "35ab0f9ba3b9b680db38732040cf6535", "packages": [ { "name": "brick/math", @@ -465,16 +465,16 @@ }, { "name": "open-telemetry/api", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0" + "reference": "8b925df3047628968bc5be722468db1b98b82d51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/74b1a03263be8c5acb578f41da054b4bac3af4a0", - "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/8b925df3047628968bc5be722468db1b98b82d51", + "reference": "8b925df3047628968bc5be722468db1b98b82d51", "shasum": "" }, "require": { @@ -531,7 +531,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-20T23:35:16+00:00" + "time": "2025-02-03T21:49:11+00:00" }, { "name": "open-telemetry/context", @@ -721,16 +721,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1" + "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1", - "reference": "96aeaee5b7cb8c0bc4af7ff4717b429f2d9f67e1", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0", + "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0", "shasum": "" }, "require": { @@ -807,24 +807,24 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-09T23:17:14+00:00" + "time": "2025-01-29T21:40:28+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.27.1", + "version": "1.30.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6" + "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/1dba705fea74bc0718d04be26090e3697e56f4e6", - "reference": "1dba705fea74bc0718d04be26090e3697e56f4e6", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", + "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.0" }, "type": "library", "extra": { @@ -864,7 +864,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-08-28T09:20:31+00:00" + "time": "2025-02-06T00:21:48+00:00" }, { "name": "php-http/discovery", @@ -2098,22 +2098,22 @@ }, { "name": "utopia-php/database", - "version": "0.58.2", + "version": "0.59.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7934edaddb9d8aad3e6c43429adc19e7c6802d2a" + "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7934edaddb9d8aad3e6c43429adc19e7c6802d2a", - "reference": "7934edaddb9d8aad3e6c43429adc19e7c6802d2a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18", + "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", - "php": ">=8.3", + "php": ">=8.1", "utopia-php/cache": "0.11.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.58.2" + "source": "https://github.com/utopia-php/database/tree/0.59.0" }, - "time": "2025-02-04T07:29:51+00:00" + "time": "2025-02-12T08:08:29+00:00" }, { "name": "utopia-php/framework", From 3763248ea8399b4fe95dfc0009ba662125f9b528 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 17 Feb 2025 23:57:35 +1300 Subject: [PATCH 309/470] Update database --- composer.json | 2 +- composer.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index fd6c52fcd98..12970adc16b 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.59.*" + "utopia-php/database": "0.60.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index a764520f66c..d49e6aa544d 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": "35ab0f9ba3b9b680db38732040cf6535", + "content-hash": "033b1a18990079ae169acb56ca441632", "packages": [ { "name": "brick/math", @@ -2098,16 +2098,16 @@ }, { "name": "utopia-php/database", - "version": "0.59.0", + "version": "0.60.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18" + "reference": "d4e17e79bd074a9346adc790b81b435eadecf614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18", - "reference": "0eed7f1ad3eb66ff4a7d73b68dd9d3e05089eb18", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d4e17e79bd074a9346adc790b81b435eadecf614", + "reference": "d4e17e79bd074a9346adc790b81b435eadecf614", "shasum": "" }, "require": { @@ -2148,9 +2148,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.59.0" + "source": "https://github.com/utopia-php/database/tree/0.60.0" }, - "time": "2025-02-12T08:08:29+00:00" + "time": "2025-02-17T10:55:07+00:00" }, { "name": "utopia-php/framework", @@ -2602,16 +2602,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "024473a478be9df5fdaca2c793f2232fe788e414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", + "reference": "024473a478be9df5fdaca2c793f2232fe788e414", "shasum": "" }, "require": { @@ -2650,7 +2650,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" }, "funding": [ { @@ -2658,7 +2658,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-02-12T12:17:51+00:00" }, { "name": "nikic/php-parser", @@ -2987,16 +2987,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.16", + "version": "1.12.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9" + "reference": "fef9f07814a573399229304bb0046affdf558812" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fef9f07814a573399229304bb0046affdf558812", + "reference": "fef9f07814a573399229304bb0046affdf558812", "shasum": "" }, "require": { @@ -3041,7 +3041,7 @@ "type": "github" } ], - "time": "2025-01-21T14:50:05+00:00" + "time": "2025-02-13T12:44:44+00:00" }, { "name": "phpunit/php-code-coverage", From bc3e917cfe6f2e199ba85f5923be12786d810a43 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 6 Mar 2025 16:16:35 +1300 Subject: [PATCH 310/470] Track latest database --- composer.json | 2 +- composer.lock | 188 +++++++++++++++++++++++--------------------------- 2 files changed, 89 insertions(+), 101 deletions(-) diff --git a/composer.json b/composer.json index 12970adc16b..dc94ca53516 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.60.*" + "utopia-php/database": "0.*.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index d49e6aa544d..574df80c93e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "033b1a18990079ae169acb56ca441632", + "content-hash": "de838692bf6a19165c97e1d9fa5ce2bf", "packages": [ { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "composer/semver", @@ -149,16 +149,16 @@ }, { "name": "google/protobuf", - "version": "v4.29.3", + "version": "v4.30.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7" + "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", - "reference": "ab5077c2cfdd1f415f42d11fdbdf903ba8e3d9b7", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/e1d66682f6836aa87820400f0aa07d9eb566feb6", + "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6", "shasum": "" }, "require": { @@ -187,9 +187,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.29.3" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.0" }, - "time": "2025-01-08T21:00:13+00:00" + "time": "2025-03-04T22:54:49+00:00" }, { "name": "jean85/pretty-package-versions", @@ -465,16 +465,16 @@ }, { "name": "open-telemetry/api", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "8b925df3047628968bc5be722468db1b98b82d51" + "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/8b925df3047628968bc5be722468db1b98b82d51", - "reference": "8b925df3047628968bc5be722468db1b98b82d51", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", + "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", "shasum": "" }, "require": { @@ -531,7 +531,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-02-03T21:49:11+00:00" + "time": "2025-03-05T21:42:54+00:00" }, { "name": "open-telemetry/context", @@ -1210,16 +1210,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", "shasum": "" }, "require": { @@ -1227,25 +1227,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -1283,19 +1280,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-02T04:48:29+00:00" }, { "name": "ramsey/uuid", @@ -1458,16 +1445,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.3", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", "shasum": "" }, "require": { @@ -1533,7 +1520,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.3" + "source": "https://github.com/symfony/http-client/tree/v7.2.4" }, "funding": [ { @@ -1549,7 +1536,7 @@ "type": "tidelift" } ], - "time": "2025-01-28T15:51:35+00:00" + "time": "2025-02-13T10:27:23+00:00" }, { "name": "symfony/http-client-contracts", @@ -2002,23 +1989,24 @@ }, { "name": "utopia-php/cache", - "version": "0.11.0", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc" + "reference": "646038f1d470b759c129348be8fc14da3c00bbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ebcab5aac7606331cef69b0081f6c9eff2e58bc", - "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/646038f1d470b759c129348be8fc14da3c00bbd9", + "reference": "646038f1d470b759c129348be8fc14da3c00bbd9", "shasum": "" }, "require": { "ext-json": "*", "ext-memcached": "*", "ext-redis": "*", - "php": ">=8.0" + "php": ">=8.0", + "utopia-php/telemetry": "0.1.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2046,9 +2034,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.11.0" + "source": "https://github.com/utopia-php/cache/tree/0.12.0" }, - "time": "2024-11-05T16:53:58+00:00" + "time": "2025-02-25T09:09:21+00:00" }, { "name": "utopia-php/compression", @@ -2098,23 +2086,23 @@ }, { "name": "utopia-php/database", - "version": "0.60.0", + "version": "0.61.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d4e17e79bd074a9346adc790b81b435eadecf614" + "reference": "7961014d5ee50c05a45653c1b91e37fcf18aed3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d4e17e79bd074a9346adc790b81b435eadecf614", - "reference": "d4e17e79bd074a9346adc790b81b435eadecf614", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7961014d5ee50c05a45653c1b91e37fcf18aed3e", + "reference": "7961014d5ee50c05a45653c1b91e37fcf18aed3e", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.1", - "utopia-php/cache": "0.11.*", + "utopia-php/cache": "0.12.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, @@ -2148,22 +2136,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.60.0" + "source": "https://github.com/utopia-php/database/tree/0.61.0" }, - "time": "2025-02-17T10:55:07+00:00" + "time": "2025-03-06T03:13:06+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.16", + "version": "0.33.17", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "e91d4c560d1b809e25faa63d564fef034363b50f" + "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/e91d4c560d1b809e25faa63d564fef034363b50f", - "reference": "e91d4c560d1b809e25faa63d564fef034363b50f", + "url": "https://api.github.com/repos/utopia-php/http/zipball/73fac6fbce9f56282dba4e52a58cf836ec434644", + "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644", "shasum": "" }, "require": { @@ -2195,9 +2183,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.16" + "source": "https://github.com/utopia-php/http/tree/0.33.17" }, - "time": "2025-01-16T15:58:50+00:00" + "time": "2025-02-24T17:35:48+00:00" }, { "name": "utopia-php/mongo", @@ -2536,16 +2524,16 @@ }, { "name": "laravel/pint", - "version": "v1.20.0", + "version": "v1.21.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b" + "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/53072e8ea22213a7ed168a8a15b96fbb8b82d44b", - "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b", + "url": "https://api.github.com/repos/laravel/pint/zipball/531fa0871fbde719c51b12afa3a443b8f4e4b425", + "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425", "shasum": "" }, "require": { @@ -2553,15 +2541,15 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.1.0" + "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.66.0", - "illuminate/view": "^10.48.25", - "larastan/larastan": "^2.9.12", - "laravel-zero/framework": "^10.48.25", + "friendsofphp/php-cs-fixer": "^3.68.5", + "illuminate/view": "^11.42.0", + "larastan/larastan": "^3.0.4", + "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^1.17.0", + "nunomaduro/termwind": "^2.3", "pestphp/pest": "^2.36.0" }, "bin": [ @@ -2598,7 +2586,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-01-14T16:20:53+00:00" + "time": "2025-02-18T03:18:57+00:00" }, { "name": "myclabs/deep-copy", @@ -2987,16 +2975,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.18", + "version": "1.12.20", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "fef9f07814a573399229304bb0046affdf558812" + "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fef9f07814a573399229304bb0046affdf558812", - "reference": "fef9f07814a573399229304bb0046affdf558812", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3240b1972042c7f73cf1045e879ea5bd5f761bb7", + "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7", "shasum": "" }, "require": { @@ -3041,7 +3029,7 @@ "type": "github" } ], - "time": "2025-02-13T12:44:44+00:00" + "time": "2025-03-05T13:37:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5071,16 +5059,16 @@ }, { "name": "symfony/process", - "version": "v7.2.0", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", "shasum": "" }, "require": { @@ -5112,7 +5100,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.2.4" }, "funding": [ { @@ -5128,7 +5116,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2025-02-05T08:33:46+00:00" }, { "name": "symfony/string", From 899feb6be8771546c6ead01f4ed2126fd2f009a5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 13 Aug 2025 21:11:17 +1200 Subject: [PATCH 311/470] Update lock --- composer.json | 2 +- composer.lock | 835 +++++++++++++++++++++----------------------------- 2 files changed, 344 insertions(+), 493 deletions(-) diff --git a/composer.json b/composer.json index dc94ca53516..859b91d8bbc 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "0.*.*" + "utopia-php/database": "1.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 574df80c93e..2dd37af78b7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "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": "f8cd992c565241eb2351765ef7f89cb6", "packages": [ { "name": "brick/math", - "version": "0.12.3", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "composer/semver", @@ -149,16 +149,16 @@ }, { "name": "google/protobuf", - "version": "v4.30.0", + "version": "v4.31.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6" + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/e1d66682f6836aa87820400f0aa07d9eb566feb6", - "reference": "e1d66682f6836aa87820400f0aa07d9eb566feb6", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", "shasum": "" }, "require": { @@ -187,137 +187,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" }, - "time": "2025-03-04T22:54:49+00:00" - }, - { - "name": "jean85/pretty-package-versions", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.1.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.6", - "vimeo/psalm": "^4.3 || ^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" - }, - "time": "2024-11-18T16:19:46+00:00" - }, - { - "name": "mongodb/mongodb", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488", - "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488", - "shasum": "" - }, - "require": { - "ext-hash": "*", - "ext-json": "*", - "ext-mongodb": "^1.11.0", - "jean85/pretty-package-versions": "^1.2 || ^2.0.1", - "php": "^7.1 || ^8.0", - "symfony/polyfill-php80": "^1.19" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0", - "squizlabs/php_codesniffer": "^3.6", - "symfony/phpunit-bridge": "^5.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0" - }, - "time": "2021-10-20T22:22:37+00:00" + "time": "2025-05-28T18:52:35+00:00" }, { "name": "nyholm/psr7", @@ -465,16 +337,16 @@ }, { "name": "open-telemetry/api", - "version": "1.2.3", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1" + "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", - "reference": "199d7ddda88f5f5619fa73463f1a5a7149ccd1f1", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", + "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", "shasum": "" }, "require": { @@ -494,7 +366,7 @@ ] }, "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.4.x-dev" } }, "autoload": { @@ -531,20 +403,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-03-05T21:42:54+00:00" + "time": "2025-06-19T23:36:51+00:00" }, { "name": "open-telemetry/context", - "version": "1.1.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3" + "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/0cba875ea1953435f78aec7f1d75afa87bdbf7f3", - "reference": "0cba875ea1953435f78aec7f1d75afa87bdbf7f3", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", + "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", "shasum": "" }, "require": { @@ -590,20 +462,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2024-08-21T00:29:20+00:00" + "time": "2025-08-04T03:25:06+00:00" }, { "name": "open-telemetry/exporter-otlp", - "version": "1.2.0", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "243d9657c44a06f740cf384f486afe954c2b725f" + "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/243d9657c44a06f740cf384f486afe954c2b725f", - "reference": "243d9657c44a06f740cf384f486afe954c2b725f", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", + "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", "shasum": "" }, "require": { @@ -654,7 +526,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-08T23:50:03+00:00" + "time": "2025-06-16T00:24:51+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -721,22 +593,22 @@ }, { "name": "open-telemetry/sdk", - "version": "1.2.2", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0" + "reference": "86287cf30fd6549444d7b8f7d8758d92e24086ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/37eec0fe47ddd627911f318f29b6cd48196be0c0", - "reference": "37eec0fe47ddd627911f318f29b6cd48196be0c0", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/86287cf30fd6549444d7b8f7d8758d92e24086ac", + "reference": "86287cf30fd6549444d7b8f7d8758d92e24086ac", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "~1.0 || ~1.1", + "open-telemetry/api": "~1.4.0", "open-telemetry/context": "^1.0", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -749,7 +621,7 @@ "ramsey/uuid": "^3.0 || ^4.0", "symfony/polyfill-mbstring": "^1.23", "symfony/polyfill-php82": "^1.26", - "tbachert/spi": "^1.0.1" + "tbachert/spi": "^1.0.5" }, "suggest": { "ext-gmp": "To support unlimited number of synchronous metric readers", @@ -759,6 +631,13 @@ "type": "library", "extra": { "spi": { + "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", + "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" + ], + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ + "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" + ], "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" ] @@ -807,20 +686,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-29T21:40:28+00:00" + "time": "2025-08-06T03:07:06+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.30.0", + "version": "1.36.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a" + "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", - "reference": "4178c9f390da8e4dbca9b181a9d1efd50cf7ee0a", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/60dd18fd21d45e6f4234ecab89c14021b6e3de9a", + "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a", "shasum": "" }, "require": { @@ -864,7 +743,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-02-06T00:21:48+00:00" + "time": "2025-08-04T03:22:08+00:00" }, { "name": "php-http/discovery", @@ -1210,16 +1089,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -1280,27 +1159,26 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", - "ext-json": "*", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1308,26 +1186,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -1362,32 +1237,22 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -1400,7 +1265,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1425,7 +1290,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1441,20 +1306,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/http-client", - "version": "v7.2.4", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" + "reference": "1c064a0c67749923483216b081066642751cc2c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7", + "reference": "1c064a0c67749923483216b081066642751cc2c7", "shasum": "" }, "require": { @@ -1466,6 +1331,7 @@ }, "conflict": { "amphp/amp": "<2.5", + "amphp/socket": "<1.1", "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.4" }, @@ -1478,7 +1344,6 @@ "require-dev": { "amphp/http-client": "^4.2.1|^5.0", "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", @@ -1520,7 +1385,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.4" + "source": "https://github.com/symfony/http-client/tree/v7.3.2" }, "funding": [ { @@ -1531,25 +1396,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -1562,7 +1431,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1598,7 +1467,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -1614,23 +1483,24 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -1678,87 +1548,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -1774,11 +1564,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", @@ -1834,7 +1624,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.32.0" }, "funding": [ { @@ -1854,16 +1644,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -1881,7 +1671,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1917,7 +1707,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -1933,20 +1723,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "tbachert/spi", - "version": "v1.0.2", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/Nevay/spi.git", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062" + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/2ddfaf815dafb45791a61b08170de8d583c16062", - "reference": "2ddfaf815dafb45791a61b08170de8d583c16062", + "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", + "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", "shasum": "" }, "require": { @@ -1964,7 +1754,7 @@ "extra": { "class": "Nevay\\SPI\\Composer\\Plugin", "branch-alias": { - "dev-main": "0.2.x-dev" + "dev-main": "1.0.x-dev" }, "plugin-optional": true }, @@ -1983,22 +1773,22 @@ ], "support": { "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.2" + "source": "https://github.com/Nevay/spi/tree/v1.0.5" }, - "time": "2024-10-04T16:36:12+00:00" + "time": "2025-06-29T15:42:06+00:00" }, { "name": "utopia-php/cache", - "version": "0.12.0", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "646038f1d470b759c129348be8fc14da3c00bbd9" + "reference": "97220cb3b3822b166ee016d1646e2ae2815dc540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/646038f1d470b759c129348be8fc14da3c00bbd9", - "reference": "646038f1d470b759c129348be8fc14da3c00bbd9", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/97220cb3b3822b166ee016d1646e2ae2815dc540", + "reference": "97220cb3b3822b166ee016d1646e2ae2815dc540", "shasum": "" }, "require": { @@ -2006,6 +1796,7 @@ "ext-memcached": "*", "ext-redis": "*", "php": ">=8.0", + "utopia-php/pools": "0.8.*", "utopia-php/telemetry": "0.1.*" }, "require-dev": { @@ -2034,9 +1825,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.12.0" + "source": "https://github.com/utopia-php/cache/tree/0.13.1" }, - "time": "2025-02-25T09:09:21+00:00" + "time": "2025-05-09T14:43:52+00:00" }, { "name": "utopia-php/compression", @@ -2086,25 +1877,25 @@ }, { "name": "utopia-php/database", - "version": "0.61.0", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7961014d5ee50c05a45653c1b91e37fcf18aed3e" + "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7961014d5ee50c05a45653c1b91e37fcf18aed3e", - "reference": "7961014d5ee50c05a45653c1b91e37fcf18aed3e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/eaa4e275cefdeeb90bcece2f056e05b59f5b1473", + "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.1", - "utopia-php/cache": "0.12.*", + "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.3.*" + "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -2136,22 +1927,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.61.0" + "source": "https://github.com/utopia-php/database/tree/1.0.0" }, - "time": "2025-03-06T03:13:06+00:00" + "time": "2025-08-11T13:56:31+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.17", + "version": "0.33.20", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644" + "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/73fac6fbce9f56282dba4e52a58cf836ec434644", - "reference": "73fac6fbce9f56282dba4e52a58cf836ec434644", + "url": "https://api.github.com/repos/utopia-php/http/zipball/e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", + "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", "shasum": "" }, "require": { @@ -2183,40 +1974,37 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.17" + "source": "https://github.com/utopia-php/http/tree/0.33.20" }, - "time": "2025-02-24T17:35:48+00:00" + "time": "2025-05-18T23:51:21+00:00" }, { - "name": "utopia-php/mongo", - "version": "0.3.1", + "name": "utopia-php/pools", + "version": "0.8.2", "source": { "type": "git", - "url": "https://github.com/utopia-php/mongo.git", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee" + "url": "https://github.com/utopia-php/pools.git", + "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", - "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", + "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", "shasum": "" }, "require": { - "ext-mongodb": "*", - "mongodb/mongodb": "1.10.0", - "php": ">=8.0" + "php": ">=8.3", + "utopia-php/telemetry": "0.1.*" }, "require-dev": { - "fakerphp/faker": "^1.14", - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.8.*", - "phpunit/phpunit": "^9.4", - "swoole/ide-helper": "4.8.0" + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\Mongo\\": "src" + "Utopia\\Pools\\": "src/Pools" } }, "notification-url": "https://packagist.org/downloads/", @@ -2225,40 +2013,35 @@ ], "authors": [ { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Wess", - "email": "wess@appwrite.io" + "name": "Team Appwrite", + "email": "team@appwrite.io" } ], - "description": "A simple library to manage Mongo database", + "description": "A simple library to manage connection pools", "keywords": [ - "database", - "mongo", + "framework", "php", - "upf", + "pools", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.3.1" + "issues": "https://github.com/utopia-php/pools/issues", + "source": "https://github.com/utopia-php/pools/tree/0.8.2" }, - "time": "2023-09-01T17:25:28+00:00" + "time": "2025-04-17T02:04:54+00:00" }, { "name": "utopia-php/telemetry", - "version": "0.1.0", + "version": "0.1.1", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80" + "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", - "reference": "d35f2f0632f4ee0be63fb7ace6a94a6adda71a80", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/437f0021777f0e575dfb9e8a1a081b3aed75e33f", + "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f", "shasum": "" }, "require": { @@ -2279,7 +2062,7 @@ "type": "library", "autoload": { "psr-4": { - "Utopia\\": "src/" + "Utopia\\Telemetry\\": "src/Telemetry" } }, "notification-url": "https://packagist.org/downloads/", @@ -2293,9 +2076,9 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.1.0" + "source": "https://github.com/utopia-php/telemetry/tree/0.1.1" }, - "time": "2024-11-13T10:29:53+00:00" + "time": "2025-03-17T11:57:52+00:00" } ], "packages-dev": [ @@ -2524,16 +2307,16 @@ }, { "name": "laravel/pint", - "version": "v1.21.0", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425" + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/531fa0871fbde719c51b12afa3a443b8f4e4b425", - "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", "shasum": "" }, "require": { @@ -2544,12 +2327,12 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.68.5", - "illuminate/view": "^11.42.0", - "larastan/larastan": "^3.0.4", - "laravel-zero/framework": "^11.36.1", + "friendsofphp/php-cs-fixer": "^3.82.2", + "illuminate/view": "^11.45.1", + "larastan/larastan": "^3.5.0", + "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3", + "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" }, "bin": [ @@ -2557,6 +2340,9 @@ ], "type": "project", "autoload": { + "files": [ + "overrides/Runner/Parallel/ProcessFactory.php" + ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -2586,20 +2372,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-02-18T03:18:57+00:00" + "time": "2025-07-10T18:09:32+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -2638,7 +2424,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -2646,20 +2432,20 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { @@ -2702,9 +2488,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "phar-io/manifest", @@ -2877,16 +2663,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "4248817222514421cba466bfa7adc7d8932345d4" + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/4248817222514421cba466bfa7adc7d8932345d4", - "reference": "4248817222514421cba466bfa7adc7d8932345d4", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", "shasum": "" }, "require": { @@ -2963,7 +2749,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.0" + "source": "https://github.com/phpbench/phpbench/tree/1.4.1" }, "funding": [ { @@ -2971,20 +2757,20 @@ "type": "github" } ], - "time": "2025-01-26T19:54:45+00:00" + "time": "2025-03-12T08:01:40+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.20", + "version": "1.12.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7" + "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3240b1972042c7f73cf1045e879ea5bd5f761bb7", - "reference": "3240b1972042c7f73cf1045e879ea5bd5f761bb7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", + "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", "shasum": "" }, "require": { @@ -3029,7 +2815,7 @@ "type": "github" } ], - "time": "2025-03-05T13:37:43+00:00" + "time": "2025-07-17T17:15:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3352,16 +3138,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea49afa29aeea25ea7bf9de9fdd7cab163cc0701", + "reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701", "shasum": "" }, "require": { @@ -3372,7 +3158,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -3383,11 +3169,11 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", + "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", + "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", "sebastian/type": "^3.2.1", @@ -3435,7 +3221,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.24" }, "funding": [ { @@ -3446,12 +3232,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-08-10T08:32:42+00:00" }, { "name": "psr/cache", @@ -3671,16 +3465,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", "shasum": "" }, "require": { @@ -3733,15 +3527,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-10T06:51:50+00:00" }, { "name": "sebastian/complexity", @@ -4008,16 +3814,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { @@ -4060,15 +3866,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { "name": "sebastian/lines-of-code", @@ -4241,16 +4059,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { @@ -4292,15 +4110,27 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", @@ -4531,23 +4361,24 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -4604,7 +4435,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.3.2" }, "funding": [ { @@ -4615,25 +4446,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { @@ -4670,7 +4505,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -4681,25 +4516,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -4734,7 +4573,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -4745,25 +4584,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.2.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", "shasum": "" }, "require": { @@ -4801,7 +4644,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" }, "funding": [ { @@ -4812,16 +4655,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-20T11:17:29+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4880,7 +4727,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -4900,7 +4747,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -4958,7 +4805,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -4978,7 +4825,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5039,7 +4886,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -5059,16 +4906,16 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -5100,7 +4947,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -5116,20 +4963,20 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-04-17T09:11:12+00:00" }, { "name": "symfony/string", - "version": "v7.2.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", - "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -5187,7 +5034,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -5198,12 +5045,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:31:26+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "theseer/tokenizer", From cb8707ad16f58875e107cb75fb95a34dfb03e662 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 5 Sep 2025 00:43:33 +1200 Subject: [PATCH 312/470] Update DB --- composer.json | 2 +- composer.lock | 300 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 203 insertions(+), 99 deletions(-) diff --git a/composer.json b/composer.json index 859b91d8bbc..57241f87edf 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "1.*" + "utopia-php/database": "2.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 2dd37af78b7..f1c0dddfe78 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": "f8cd992c565241eb2351765ef7f89cb6", + "content-hash": "ff99d159b5fa5a53b8be1c93a62bceee", "packages": [ { "name": "brick/math", @@ -68,16 +68,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -129,7 +129,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -139,33 +139,32 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "google/protobuf", - "version": "v4.31.1", + "version": "v4.32.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", - "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": ">=8.1.0" + }, + "provide": { + "ext-protobuf": "*" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0" + "phpunit/phpunit": ">=5.0.0 <8.5.27" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -187,9 +186,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" }, - "time": "2025-05-28T18:52:35+00:00" + "time": "2025-08-14T20:00:33+00:00" }, { "name": "nyholm/psr7", @@ -407,16 +406,16 @@ }, { "name": "open-telemetry/context", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc" + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", - "reference": "4d5d98f1d4311a55b8d07e3d4c06d2430b4e6efc", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", + "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", "shasum": "" }, "require": { @@ -462,7 +461,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-04T03:25:06+00:00" + "time": "2025-08-13T01:12:00+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -690,16 +689,16 @@ }, { "name": "open-telemetry/sem-conv", - "version": "1.36.0", + "version": "1.37.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a" + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/60dd18fd21d45e6f4234ecab89c14021b6e3de9a", - "reference": "60dd18fd21d45e6f4234ecab89c14021b6e3de9a", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", + "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", "shasum": "" }, "require": { @@ -743,7 +742,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-04T03:22:08+00:00" + "time": "2025-09-03T12:08:10+00:00" }, { "name": "php-http/discovery", @@ -1310,16 +1309,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "1c064a0c67749923483216b081066642751cc2c7" + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7", - "reference": "1c064a0c67749923483216b081066642751cc2c7", + "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", "shasum": "" }, "require": { @@ -1327,6 +1326,7 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/polyfill-php83": "^1.29", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -1385,7 +1385,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.2" + "source": "https://github.com/symfony/http-client/tree/v7.3.3" }, "funding": [ { @@ -1405,7 +1405,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-08-27T07:45:05+00:00" }, { "name": "symfony/http-client-contracts", @@ -1487,7 +1487,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -1548,7 +1548,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -1559,6 +1559,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1568,7 +1572,7 @@ }, { "name": "symfony/polyfill-php82", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", @@ -1624,7 +1628,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" }, "funding": [ { @@ -1635,6 +1639,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1642,6 +1650,86 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -1877,16 +1965,16 @@ }, { "name": "utopia-php/database", - "version": "1.0.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473" + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/eaa4e275cefdeeb90bcece2f056e05b59f5b1473", - "reference": "eaa4e275cefdeeb90bcece2f056e05b59f5b1473", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", + "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", "shasum": "" }, "require": { @@ -1927,22 +2015,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/1.0.0" + "source": "https://github.com/utopia-php/database/tree/2.0.0" }, - "time": "2025-08-11T13:56:31+00:00" + "time": "2025-09-04T12:36:53+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.20", + "version": "0.33.24", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131" + "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", - "reference": "e1c7ab4e0b5b0a9a70256b1e00912e101e76a131", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5112b1023342163e3fbedec99f38fc32c8700aa0", + "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0", "shasum": "" }, "require": { @@ -1974,9 +2062,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.20" + "source": "https://github.com/utopia-php/http/tree/0.33.24" }, - "time": "2025-05-18T23:51:21+00:00" + "time": "2025-09-04T04:18:39+00:00" }, { "name": "utopia-php/pools", @@ -2436,16 +2524,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -2464,7 +2552,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -2488,9 +2576,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-07-27T20:03:57+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -3138,16 +3226,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.24", + "version": "9.6.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701" + "reference": "049c011e01be805202d8eebedef49f769a8ec7b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea49afa29aeea25ea7bf9de9fdd7cab163cc0701", - "reference": "ea49afa29aeea25ea7bf9de9fdd7cab163cc0701", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/049c011e01be805202d8eebedef49f769a8ec7b7", + "reference": "049c011e01be805202d8eebedef49f769a8ec7b7", "shasum": "" }, "require": { @@ -3221,7 +3309,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.25" }, "funding": [ { @@ -3245,7 +3333,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:32:42+00:00" + "time": "2025-08-20T14:38:31+00:00" }, { "name": "psr/cache", @@ -4361,16 +4449,16 @@ }, { "name": "symfony/console", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { @@ -4435,7 +4523,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.2" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -4455,7 +4543,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:13:41+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/filesystem", @@ -4597,16 +4685,16 @@ }, { "name": "symfony/options-resolver", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", - "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", "shasum": "" }, "require": { @@ -4644,7 +4732,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" }, "funding": [ { @@ -4664,11 +4752,11 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-08-05T10:16:07+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4727,7 +4815,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -4738,6 +4826,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4747,16 +4839,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -4805,7 +4897,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -4816,16 +4908,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4886,7 +4982,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -4897,6 +4993,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4906,16 +5006,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -4947,7 +5047,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -4958,25 +5058,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/string", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { @@ -5034,7 +5138,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -5054,7 +5158,7 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "theseer/tokenizer", From cb92c592e1accca890644b9989540827ad2c5e8b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Oct 2025 18:59:29 +1300 Subject: [PATCH 313/470] Fix PSR autoload --- tests/Abuse/Bench/Base.php | 10 +++++----- tests/Abuse/Bench/Database.php | 21 +++++++++++++-------- tests/Abuse/Bench/Redis.php | 5 ++--- tests/Abuse/Bench/RedisCluster.php | 9 ++++----- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tests/Abuse/Bench/Base.php b/tests/Abuse/Bench/Base.php index e78d7af89b8..018a706139c 100644 --- a/tests/Abuse/Bench/Base.php +++ b/tests/Abuse/Bench/Base.php @@ -2,12 +2,12 @@ namespace Utopia\Tests\Bench; -use Utopia\Abuse\Abuse; -use Utopia\Abuse\Adapters\TimeLimit; use PhpBench\Attributes\BeforeMethods; use PhpBench\Attributes\Iterations; use PhpBench\Attributes\OutputMode; use PhpBench\Attributes\OutputTimeUnit; +use Utopia\Abuse\Abuse; +use Utopia\Abuse\Adapters\TimeLimit; abstract class Base { @@ -15,17 +15,17 @@ abstract class Base protected TimeLimit $adapter; #[BeforeMethods('setUp')] - #[Iterations([20,30,50])] + #[Iterations([20, 30, 50])] #[OutputMode('throughput')] #[OutputTimeUnit('millisecond')] public function benchTimelimit(): void { $ip = ''; for ($i = 0; $i < 4; $i++) { - $sub = rand(0, 255); + $sub = \rand(0, 255); $ip .= $sub . '.'; }; - $ip = rtrim($ip, '.'); + $ip = \rtrim($ip, '.'); $this->adapter->setParam('{{ip}}', $ip); $this->abuse->check(); } diff --git a/tests/Abuse/Bench/Database.php b/tests/Abuse/Bench/Database.php index a24c9ed4a78..c5feb08f40a 100644 --- a/tests/Abuse/Bench/Database.php +++ b/tests/Abuse/Bench/Database.php @@ -1,6 +1,6 @@ setDatabase('utopiaTests'); $db->setNamespace('namespace'); $this->db = $db; $adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $db); - if (! $db->exists('utopiaTests')) { + if (!$db->exists('utopiaTests')) { $db->create(); $adapter->setup(); } diff --git a/tests/Abuse/Bench/Redis.php b/tests/Abuse/Bench/Redis.php index 882741a4715..c7518518aa4 100644 --- a/tests/Abuse/Bench/Redis.php +++ b/tests/Abuse/Bench/Redis.php @@ -1,13 +1,12 @@ redis = new Client(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); - $this->adapter = new RedisCluster('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); + $this->adapter = new RedisClusterAdapter('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); $this->abuse = new Abuse($this->adapter); } } From 79ac753c6bd2b9369d513ed3b6e0932f66e905f1 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Oct 2025 18:59:34 +1300 Subject: [PATCH 314/470] Unpin DB --- composer.json | 2 +- composer.lock | 465 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 344 insertions(+), 123 deletions(-) diff --git a/composer.json b/composer.json index 57241f87edf..80a4df57c0a 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "2.*" + "utopia-php/database": "*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index f1c0dddfe78..4cec497f7cd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ff99d159b5fa5a53b8be1c93a62bceee", + "content-hash": "0c308f58a2b6a5c71a18543ae5879dbf", "packages": [ { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.13.1" + "source": "https://github.com/brick/math/tree/0.14.0" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-08-29T12:40:03+00:00" }, { "name": "composer/semver", @@ -145,24 +145,21 @@ }, { "name": "google/protobuf", - "version": "v4.32.0", + "version": "v4.33.0", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646" + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", - "reference": "9a9a92ecbe9c671dc1863f6d4a91ea3ea12c8646", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d", + "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d", "shasum": "" }, "require": { "php": ">=8.1.0" }, - "provide": { - "ext-protobuf": "*" - }, "require-dev": { "phpunit/phpunit": ">=5.0.0 <8.5.27" }, @@ -186,9 +183,86 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.32.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0" }, - "time": "2025-08-14T20:00:33+00:00" + "time": "2025-10-15T20:10:28+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac", + "reference": "f399d24905dd42f97dfe0af9706129743ef247ac", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "ext-mongodb": "^2.1", + "php": "^8.1", + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.32" + }, + "replace": { + "mongodb/builder": "*" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^1.2", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "6.5.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "MongoDB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + }, + { + "name": "Jérôme Tamarelle", + "email": "jerome.tamarelle@mongodb.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1" + }, + "time": "2025-08-13T20:50:05+00:00" }, { "name": "nyholm/psr7", @@ -336,20 +410,20 @@ }, { "name": "open-telemetry/api", - "version": "1.4.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7" + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", - "reference": "b3a9286f9c1c8247c83493c5b1fa475cd0cec7f7", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", "shasum": "" }, "require": { - "open-telemetry/context": "^1.0", + "open-telemetry/context": "^1.4", "php": "^8.1", "psr/log": "^1.1|^2.0|^3.0", "symfony/polyfill-php82": "^1.26" @@ -365,7 +439,7 @@ ] }, "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.7.x-dev" } }, "autoload": { @@ -402,20 +476,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-06-19T23:36:51+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/context", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6" + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/438f71812242db3f196fb4c717c6f92cbc819be6", - "reference": "438f71812242db3f196fb4c717c6f92cbc819be6", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", "shasum": "" }, "require": { @@ -461,7 +535,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-13T01:12:00+00:00" + "time": "2025-09-19T00:05:49+00:00" }, { "name": "open-telemetry/exporter-otlp", @@ -529,16 +603,16 @@ }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.5.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0" + "reference": "673af5b06545b513466081884b47ef15a536edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/585bafddd4ae6565de154610b10a787a455c9ba0", - "reference": "585bafddd4ae6565de154610b10a787a455c9ba0", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", + "reference": "673af5b06545b513466081884b47ef15a536edde", "shasum": "" }, "require": { @@ -588,27 +662,27 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-01-15T23:07:07+00:00" + "time": "2025-09-17T23:10:12+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.7.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "86287cf30fd6549444d7b8f7d8758d92e24086ac" + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/86287cf30fd6549444d7b8f7d8758d92e24086ac", - "reference": "86287cf30fd6549444d7b8f7d8758d92e24086ac", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "~1.4.0", - "open-telemetry/context": "^1.0", + "open-telemetry/api": "^1.7", + "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", "php-http/discovery": "^1.14", @@ -642,7 +716,7 @@ ] }, "branch-alias": { - "dev-main": "1.0.x-dev" + "dev-main": "1.9.x-dev" } }, "autoload": { @@ -685,7 +759,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-08-06T03:07:06+00:00" + "time": "2025-10-02T23:44:28+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1164,20 +1238,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1236,9 +1310,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.0" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "time": "2025-06-25T14:20:11+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1309,16 +1383,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019" + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", - "reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", "shasum": "" }, "require": { @@ -1385,7 +1459,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.3" + "source": "https://github.com/symfony/http-client/tree/v7.3.4" }, "funding": [ { @@ -1405,7 +1479,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T07:45:05+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/http-client-contracts", @@ -1730,6 +1804,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -1965,29 +2119,31 @@ }, { "name": "utopia-php/database", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5" + "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e4a03ba543abc4e436ec1b450750a14bd36011d5", - "reference": "e4a03ba543abc4e436ec1b450750a14bd36011d5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/d41fd5649e01cc84be840dee95a7bf47eec891a5", + "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5", "shasum": "" }, "require": { "ext-mbstring": "*", + "ext-mongodb": "*", "ext-pdo": "*", "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", + "utopia-php/mongo": "0.10.*", "utopia-php/pools": "0.8.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.*", + "laravel/pint": "*", "pcov/clobber": "2.*", "phpstan/phpstan": "1.*", "phpunit/phpunit": "9.*", @@ -2015,22 +2171,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/2.0.0" + "source": "https://github.com/utopia-php/database/tree/3.0.0" }, - "time": "2025-09-04T12:36:53+00:00" + "time": "2025-10-20T05:51:31+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.24", + "version": "0.33.28", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0" + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/5112b1023342163e3fbedec99f38fc32c8700aa0", - "reference": "5112b1023342163e3fbedec99f38fc32c8700aa0", + "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", + "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", "shasum": "" }, "require": { @@ -2062,9 +2218,70 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.24" + "source": "https://github.com/utopia-php/http/tree/0.33.28" + }, + "time": "2025-09-25T10:44:24+00:00" + }, + { + "name": "utopia-php/mongo", + "version": "0.10.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/mongo.git", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "shasum": "" + }, + "require": { + "ext-mongodb": "2.1.*", + "mongodb/mongodb": "2.1.*", + "php": ">=8.0", + "ramsey/uuid": "4.9.*" + }, + "require-dev": { + "fakerphp/faker": "1.*", + "laravel/pint": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "9.*", + "swoole/ide-helper": "5.1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Mongo\\": "src" + } }, - "time": "2025-09-04T04:18:39+00:00" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eldad Fux", + "email": "eldad@appwrite.io" + }, + { + "name": "Wess", + "email": "wess@appwrite.io" + } + ], + "description": "A simple library to manage Mongo database", + "keywords": [ + "database", + "mongo", + "php", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/mongo/issues", + "source": "https://github.com/utopia-php/mongo/tree/0.10.0" + }, + "time": "2025-10-02T04:50:07+00:00" }, { "name": "utopia-php/pools", @@ -2244,6 +2461,7 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, { @@ -2395,16 +2613,16 @@ }, { "name": "laravel/pint", - "version": "v1.24.0", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", - "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", + "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", "shasum": "" }, "require": { @@ -2415,9 +2633,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.82.2", - "illuminate/view": "^11.45.1", - "larastan/larastan": "^3.5.0", + "friendsofphp/php-cs-fixer": "^3.87.2", + "illuminate/view": "^11.46.0", + "larastan/larastan": "^3.7.1", "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", @@ -2428,9 +2646,6 @@ ], "type": "project", "autoload": { - "files": [ - "overrides/Runner/Parallel/ProcessFactory.php" - ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -2460,7 +2675,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-07-10T18:09:32+00:00" + "time": "2025-09-19T02:57:12+00:00" }, { "name": "myclabs/deep-copy", @@ -2849,16 +3064,11 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.28", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9" - }, + "version": "1.12.32", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", - "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", + "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", "shasum": "" }, "require": { @@ -2903,7 +3113,7 @@ "type": "github" } ], - "time": "2025-07-17T17:15:39+00:00" + "time": "2025-09-30T10:16:31+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3226,16 +3436,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.25", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "049c011e01be805202d8eebedef49f769a8ec7b7" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/049c011e01be805202d8eebedef49f769a8ec7b7", - "reference": "049c011e01be805202d8eebedef49f769a8ec7b7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -3260,7 +3470,7 @@ "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", + "sebastian/exporter": "^4.0.8", "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", @@ -3309,7 +3519,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -3333,7 +3543,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:38:31+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "psr/cache", @@ -3825,16 +4035,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -3890,15 +4100,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", @@ -4449,16 +4671,16 @@ }, { "name": "symfony/console", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", - "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -4523,7 +4745,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.3" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -4543,7 +4765,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/filesystem", @@ -5006,16 +5228,16 @@ }, { "name": "symfony/process", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", - "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -5047,7 +5269,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.3" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -5067,20 +5289,20 @@ "type": "tidelift" } ], - "time": "2025-08-18T09:42:54+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/string", - "version": "v7.3.3", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", - "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -5095,7 +5317,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -5138,7 +5359,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -5158,7 +5379,7 @@ "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "theseer/tokenizer", From f517e661517c3d527366b4f82ab3f9c0678902ce Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Oct 2025 20:07:12 +1300 Subject: [PATCH 315/470] Fix image refs --- docker-compose.yml | 8 ++++---- tests/Abuse/Bench/Database.php | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 17566973868..fda4da8828e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: - redisinsight:/data redis-cluster-0: - image: docker.io/bitnami/redis-cluster:7.4 + image: bitnamilegacy/redis-cluster:7.4 environment: - ALLOW_EMPTY_PASSWORD=yes - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 @@ -64,7 +64,7 @@ services: - redis-cluster-3 redis-cluster-1: - image: docker.io/bitnami/redis-cluster:7.4 + image: bitnamilegacy/redis-cluster:7.4 environment: - ALLOW_EMPTY_PASSWORD=yes - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 @@ -72,7 +72,7 @@ services: - abuse redis-cluster-2: - image: docker.io/bitnami/redis-cluster:7.4 + image: bitnamilegacy/redis-cluster:7.4 environment: - ALLOW_EMPTY_PASSWORD=yes - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 @@ -80,7 +80,7 @@ services: - abuse redis-cluster-3: - image: docker.io/bitnami/redis-cluster:7.4 + image: bitnamilegacy/redis-cluster:7.4 environment: - ALLOW_EMPTY_PASSWORD=yes - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 diff --git a/tests/Abuse/Bench/Database.php b/tests/Abuse/Bench/Database.php index c5feb08f40a..cbf9c6cef62 100644 --- a/tests/Abuse/Bench/Database.php +++ b/tests/Abuse/Bench/Database.php @@ -7,7 +7,6 @@ use Utopia\Abuse\Adapters\TimeLimit\Database as TimeLimit; use Utopia\Cache\Adapter\None as NoCache; use Utopia\Cache\Cache; -use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Adapter\MySQL; use Utopia\Database\Adapter\SQL; use Utopia\Database\Database as UtopiaDatabase; From 3ee93201d724bd88a9c615b79ac88fa250f09900 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Oct 2025 20:16:49 +1300 Subject: [PATCH 316/470] Remove adminer --- docker-compose.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fda4da8828e..f3a0998b7fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,15 +8,6 @@ services: ports: - "9307:3306" - adminer: - image: adminer - container_name: abuse-adminer - restart: always - ports: - - 9506:8080 - networks: - - abuse - redis: image: redis:6.0-alpine container_name: redis From 947d16c01560ac381b5e0b1d9abb6caec3d5c6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Nov 2025 21:18:14 +0100 Subject: [PATCH 317/470] Implement AppwriteTablesDB adapter --- composer.json | 3 +- composer.lock | 210 ++++++----- .../Adapters/TimeLimit/AppwriteTablesDB.php | 337 ++++++++++++++++++ tests/Abuse/AppwriteTablesDBTest.php | 43 +++ tests/Abuse/Base.php | 12 +- 5 files changed, 516 insertions(+), 89 deletions(-) create mode 100644 src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php create mode 100755 tests/Abuse/AppwriteTablesDBTest.php diff --git a/composer.json b/composer.json index 80a4df57c0a..1d2c9101499 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "*" + "utopia-php/database": "3.*.*", + "appwrite/appwrite": "18.*.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 4cec497f7cd..3dc63a55cef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,50 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0c308f58a2b6a5c71a18543ae5879dbf", + "content-hash": "835e092b19aef86b434e86b94fbf39ca", "packages": [ + { + "name": "appwrite/appwrite", + "version": "18.0.1", + "source": { + "type": "git", + "url": "https://github.com/appwrite/sdk-for-php.git", + "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/950112a73c05297e23fa2eddb68f4ce8a6024e03", + "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=7.1.0" + }, + "require-dev": { + "mockery/mockery": "^1.6.12", + "phpunit/phpunit": "^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Appwrite\\": "src/Appwrite" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API", + "support": { + "email": "team@appwrite.io", + "issues": "https://github.com/appwrite/sdk-for-php/issues", + "source": "https://github.com/appwrite/sdk-for-php/tree/18.0.1", + "url": "https://appwrite.io/support" + }, + "time": "2025-11-10T12:50:46+00:00" + }, { "name": "brick/math", "version": "0.14.0", @@ -145,16 +187,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.0", + "version": "v4.33.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d" + "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d", - "reference": "b50269e23204e5ae859a326ec3d90f09efe3047d", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12", + "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12", "shasum": "" }, "require": { @@ -183,9 +225,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1" }, - "time": "2025-10-15T20:10:28+00:00" + "time": "2025-11-12T21:58:05+00:00" }, { "name": "mongodb/mongodb", @@ -1383,16 +1425,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", "shasum": "" }, "require": { @@ -1459,7 +1501,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.4" + "source": "https://github.com/symfony/http-client/tree/v7.3.6" }, "funding": [ { @@ -1479,7 +1521,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T17:41:46+00:00" }, { "name": "symfony/http-client-contracts", @@ -1886,16 +1928,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -1949,7 +1991,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -1960,12 +2002,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "tbachert/spi", @@ -2119,16 +2165,16 @@ }, { "name": "utopia-php/database", - "version": "3.0.0", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5" + "reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d41fd5649e01cc84be840dee95a7bf47eec891a5", - "reference": "d41fd5649e01cc84be840dee95a7bf47eec891a5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/e10b4faa4f3a3ef30a5f6d76acdb605469924aec", + "reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec", "shasum": "" }, "require": { @@ -2138,7 +2184,7 @@ "php": ">=8.1", "utopia-php/cache": "0.13.*", "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.10.*", + "utopia-php/mongo": "0.11.*", "utopia-php/pools": "0.8.*" }, "require-dev": { @@ -2171,9 +2217,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/3.0.0" + "source": "https://github.com/utopia-php/database/tree/3.4.0" }, - "time": "2025-10-20T05:51:31+00:00" + "time": "2025-11-13T06:34:20+00:00" }, { "name": "utopia-php/framework", @@ -2224,16 +2270,16 @@ }, { "name": "utopia-php/mongo", - "version": "0.10.0", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18" + "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", - "reference": "ecfad6aad2e2e3fe5899ac2ebf1009a21b4d6b18", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/34bc0cda8ea368cde68702a6fffe2c3ac625398e", + "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e", "shasum": "" }, "require": { @@ -2279,9 +2325,9 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.10.0" + "source": "https://github.com/utopia-php/mongo/tree/0.11.0" }, - "time": "2025-10-02T04:50:07+00:00" + "time": "2025-10-20T11:11:23+00:00" }, { "name": "utopia-php/pools", @@ -2739,16 +2785,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -2791,9 +2837,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -2915,24 +2961,24 @@ }, { "name": "phpbench/container", - "version": "2.2.2", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/phpbench/container.git", - "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33" + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33", - "reference": "a59b929e00b87b532ca6d0edd8eca0967655af33", + "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196", + "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196", "shasum": "" }, "require": { "psr/container": "^1.0|^2.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0" + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", + "php-cs-fixer/shim": "^3.89", "phpstan/phpstan": "^0.12.52", "phpunit/phpunit": "^8" }, @@ -2960,22 +3006,22 @@ "description": "Simple, configurable, service container.", "support": { "issues": "https://github.com/phpbench/container/issues", - "source": "https://github.com/phpbench/container/tree/2.2.2" + "source": "https://github.com/phpbench/container/tree/2.2.3" }, - "time": "2023-10-30T13:38:26+00:00" + "time": "2025-11-06T09:05:13+00:00" }, { "name": "phpbench/phpbench", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b" + "reference": "b641dde59d969ea42eed70a39f9b51950bc96878" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", - "reference": "78cd98a9aa34e0f8f80ca01972a8b88d2c30194b", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878", + "reference": "b641dde59d969ea42eed70a39f9b51950bc96878", "shasum": "" }, "require": { @@ -2990,26 +3036,26 @@ "phpbench/container": "^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", - "symfony/console": "^6.1 || ^7.0", - "symfony/filesystem": "^6.1 || ^7.0", - "symfony/finder": "^6.1 || ^7.0", - "symfony/options-resolver": "^6.1 || ^7.0", - "symfony/process": "^6.1 || ^7.0", + "symfony/console": "^6.1 || ^7.0 || ^8.0", + "symfony/filesystem": "^6.1 || ^7.0 || ^8.0", + "symfony/finder": "^6.1 || ^7.0 || ^8.0", + "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0", + "symfony/process": "^6.1 || ^7.0 || ^8.0", "webmozart/glob": "^4.6" }, "require-dev": { "dantleech/invoke": "^2.0", "ergebnis/composer-normalize": "^2.39", - "friendsofphp/php-cs-fixer": "^3.0", "jangregor/phpstan-prophecy": "^1.0", - "phpspec/prophecy": "dev-master", + "php-cs-fixer/shim": "^3.9", + "phpspec/prophecy": "^1.22", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^10.4 || ^11.0", "rector/rector": "^1.2", - "symfony/error-handler": "^6.1 || ^7.0", - "symfony/var-dumper": "^6.1 || ^7.0" + "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" }, "suggest": { "ext-xdebug": "For Xdebug profiling extension." @@ -3052,7 +3098,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.1" + "source": "https://github.com/phpbench/phpbench/tree/1.4.3" }, "funding": [ { @@ -3060,7 +3106,7 @@ "type": "github" } ], - "time": "2025-03-12T08:01:40+00:00" + "time": "2025-11-06T19:07:31+00:00" }, { "name": "phpstan/phpstan", @@ -4671,16 +4717,16 @@ }, { "name": "symfony/console", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -4745,7 +4791,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.4" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -4765,20 +4811,20 @@ "type": "tidelift" } ], - "time": "2025-09-22T15:31:00+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.2", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", "shasum": "" }, "require": { @@ -4815,7 +4861,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.2" + "source": "https://github.com/symfony/filesystem/tree/v7.3.6" }, "funding": [ { @@ -4835,20 +4881,20 @@ "type": "tidelift" } ], - "time": "2025-07-07T08:17:47+00:00" + "time": "2025-11-05T09:52:27+00:00" }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -4883,7 +4929,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -4903,7 +4949,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/options-resolver", @@ -5483,7 +5529,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5492,6 +5538,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php b/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php new file mode 100644 index 00000000000..d65a5bfed3e --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php @@ -0,0 +1,337 @@ +key = $key; + $now = \time(); + $this->timestamp = (int)($now - ($now % $seconds)); + $this->limit = $limit; + $this->tablesDB = new TablesDB($client); + $this->databaseId = $databaseId; + } + + /** + * @throws Exception|\Exception + */ + public function setup(): void + { + try { + $this->tablesDB->getTable($this->databaseId, self::TABLE_LOCK); + // Schema indication table exists, we can safely assume database is setup + return; + } catch (\Throwable $err) { + // Error occured, we do in-depth setup + } + + try { + $this->tablesDB->create($this->databaseId, self::DATABASE_NAME); + } catch (AppwriteException $err) { + // Silence error if database is already present + if ($err->getType() !== 'database_already_exists') { + throw $err; + } + } + + try { + $this->tablesDB->createTable($this->databaseId, self::TABLE_ID, self::TABLE_NAME); + } catch (AppwriteException $err) { + // Silence error if table is already present + if ($err->getType() !== 'table_already_exists') { + throw $err; + } + } + + try { + $this->tablesDB->createStringColumn($this->databaseId, self::TABLE_ID, 'key', 255, true); + } catch (AppwriteException $err) { + // Silence error if column is already present + if ($err->getType() !== 'column_already_exists') { + throw $err; + } + } + + try { + $this->tablesDB->createDatetimeColumn($this->databaseId, self::TABLE_ID, 'time', true); + } catch (AppwriteException $err) { + // Silence error if column is already present + if ($err->getType() !== 'column_already_exists') { + throw $err; + } + } + + try { + $this->tablesDB->createIntegerColumn($this->databaseId, self::TABLE_ID, 'count', true, 0, PHP_INT_MAX); + } catch (AppwriteException $err) { + // Silence error if column is already present + if ($err->getType() !== 'column_already_exists') { + throw $err; + } + } + + // Await till all attributes are ready + $ready = false; + $attempts = 0; + while ($attempts < 15) { + $attempts++; + + $response = $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [ + Query::notEqual('status', 'available'), + Query::limit(1) + ]); + + $columns = $response['columns']; + + // Temporary fix due to bug in Appwrite listColumns queries + $columns = \array_filter($columns, fn ($column) => $column['status'] !== 'available'); + + if (\count($columns) === 0) { + $ready = true; + break; + } + + \sleep(1); + } + + if (!$ready) { + throw new \Exception('Failed to setup tables.'); + } + + try { + $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', IndexType::UNIQUE(), ['key', 'time']); + } catch (AppwriteException $err) { + // Silence error if index is already present + if ($err->getType() !== 'index_already_exists') { + throw $err; + } + } + + try { + $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', IndexType::KEY(), ['time']); + } catch (AppwriteException $err) { + // Silence error if index is already present + if ($err->getType() !== 'index_already_exists') { + throw $err; + } + } + + // Await till all indexes are ready + $ready = false; + $attempts = 0; + while ($attempts < 15) { + $attempts++; + + $response = $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [ + Query::notEqual('status', 'available'), + Query::limit(1) + ]); + + $indexes = $response['indexes']; + + // Temporary fix due to bug in Appwrite listColumns queries + $indexes = \array_filter($indexes, fn ($index) => $index['status'] !== 'available'); + + if (\count($indexes) === 0) { + $ready = true; + break; + } + + \sleep(1); + } + + if (!$ready) { + throw new \Exception('Failed to setup tables.'); + } + + // Optimize future setup checks + try { + $this->tablesDB->createTable($this->databaseId, self::TABLE_LOCK, name: self::TABLE_LOCK); + } catch (AppwriteException $err) { + // Silence error if table is already present + if ($err->getType() !== 'table_already_exists') { + throw $err; + } + } + } + + /** + * Check + * + * Checks if number of counts is bigger or smaller than current limit + * + * @param string $key + * @param int $timestamp + * @return int + * + * @throws \Exception + */ + protected function count(string $key, int $timestamp): int + { + if (0 == $this->limit) { // No limit no point for counting + return 0; + } + + if (! \is_null($this->count)) { // Get fetched result + return $this->count; + } + + $timestamp = $this->toDateTime($timestamp); + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + + $this->count = 0; + + if (\count($rows) === 1) { // Unique Index + $count = $rows[0]['count'] ?? 0; + if (\is_numeric($count)) { + $this->count = intval($count); + } + } + + return $this->count; + } + + /** + * @param string $key + * @param int $timestamp + * @return void + * + * @throws \Throwable + */ + protected function hit(string $key, int $timestamp): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $timestamp = $this->toDateTime($timestamp); + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + $data = $rows[0] ?? null; + + if (\is_null($data)) { + $data = [ + 'key' => $key, + 'time' => $timestamp, + 'count' => 1, + ]; + + try { + $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); + } catch (AppwriteException $err) { + if ($err->getType() !== 'row_already_exists') { + throw $err; + } + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + + $data = $rows[0] ?? null; + + if (!is_null($data)) { + $count = $data['count'] ?? 0; + if (\is_numeric($count)) { + $this->count = intval($count); + } + + $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $data['$id'], 'count', 1); + } else { + throw new \Exception('Document Not Found'); + } + } + } else { + $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $data['$id'], 'count', 1); + } + + $this->count++; + } + + /** + * Get abuse logs + * + * Return logs with an optional offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + * + * @throws \Exception + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $queries = []; + + $queries[] = Query::orderDesc(''); + + if (! \is_null($offset)) { + $queries[] = Query::offset($offset); + } + if (! \is_null($limit)) { + $queries[] = Query::limit($limit); + } + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, $queries); + + return \array_map(fn ($document) => new Document($document), $response['documents']); + } + + /** + * Delete logs older than $timestamp seconds + * + * @param int $timestamp + * @return bool + * + * @throws \Exception + */ + public function cleanup(int $timestamp): bool + { + $timestamp = $this->toDateTime($timestamp); + + do { + $response = $this->tablesDB->deleteRows($this->databaseId, self::TABLE_ID, [ + Query::lessThan('time', $timestamp), + ]); + } while ($response['total'] > 0); + + return true; + } + + protected function toDateTime(int $timestamp): string + { + return (new \DateTime())->setTimestamp($timestamp)->format('Y-m-d H:i:s.v'); + } +} diff --git a/tests/Abuse/AppwriteTablesDBTest.php b/tests/Abuse/AppwriteTablesDBTest.php new file mode 100755 index 00000000000..eef531352be --- /dev/null +++ b/tests/Abuse/AppwriteTablesDBTest.php @@ -0,0 +1,43 @@ +setEndpoint('https://fra.cloud.appwrite.io/v1') + ->setProject('68f0b93e003404ce2e31') // Utopia PHP + ->setKey('standard_6e7fe493f9dbe734c77eb701982baa223bc95149b61496306ce9e03276e0f79112cd8738a178d78cc5c66f0ae8ad912a71260086f1ab6b5c18271be3c58f66c9b5e3cca22a470a220093c585a5c5b24831c3fdac6ee8fdda7b19a5a63316bf45cbb30d9bc7e84a5e2580fac24acb6273fd13d86e2b2cf830276df9afd43f59f2'); + + $adapter = new AdapterAppwriteTablesDB('', 1, 1, self::$client, self::$databaseId); + $adapter->setup(); + } + + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit + { + return new AdapterAppwriteTablesDB($key, $limit, $seconds, self::$client, self::$databaseId); + } + + public static function tearDownAfterClass(): void + { + } +} diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 5cb59c7da77..f0bc33709f7 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -50,14 +50,14 @@ public function testDynamicKeyWith2Params(): void } /** - * Test a dynamic key with higher request rate like 100 requests per second + * Test a dynamic key with higher request rate like 10 requests per second */ public function testDynamicKeyFastRequests(): void { - $adapter = $this->getAdapter('fast-requests-{{ip}}', 100, 1); + $adapter = $this->getAdapter('fast-requests-{{ip}}', 10, 1); $adapter->setParam('{{ip}}', '0.0.0.10'); $abuse = new Abuse($adapter); - for ($i = 0; $i < 100; $i++) { + for ($i = 0; $i < 10; $i++) { $this->assertEquals($abuse->check(), false); } $this->assertEquals($abuse->check(), true); @@ -68,10 +68,10 @@ public function testDynamicKeyFastRequests(): void */ public function testLimitReset(): void { - $adapter = $this->getAdapter('limit-reset-{{ip}}', 100, 2); + $adapter = $this->getAdapter('limit-reset-{{ip}}', 10, 2); $adapter->setParam('{{ip}}', '127.0.0.1'); $abuse = new Abuse($adapter); - for ($i = 0; $i < 100; $i++) { + for ($i = 0; $i < 10; $i++) { $this->assertEquals($abuse->check(), false); } $this->assertEquals($abuse->check(), true); @@ -80,7 +80,7 @@ public function testLimitReset(): void sleep(2); /** Seems to be a bug in the code where if use the same adapter, it caches the result of the previous check */ - $adapter = $this->getAdapter('limit-reset-{{ip}}', 100, 1); + $adapter = $this->getAdapter('limit-reset-{{ip}}', 10, 1); $adapter->setParam('{{ip}}', '127.0.0.1'); $abuse = new Abuse($adapter); $this->assertEquals($abuse->check(), false); From 04dc655c56ae799fa5051e3e29ce0a300db91202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Nov 2025 21:23:16 +0100 Subject: [PATCH 318/470] Shorten code --- .../Adapters/TimeLimit/AppwriteTablesDB.php | 182 ++++++++---------- 1 file changed, 76 insertions(+), 106 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php b/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php index d65a5bfed3e..102c65dc293 100644 --- a/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php @@ -39,139 +39,109 @@ public function __construct(string $key, int $limit, int $seconds, Client $clien */ public function setup(): void { - try { - $this->tablesDB->getTable($this->databaseId, self::TABLE_LOCK); - // Schema indication table exists, we can safely assume database is setup + if ($this->isSetupComplete()) { return; - } catch (\Throwable $err) { - // Error occured, we do in-depth setup - } - - try { - $this->tablesDB->create($this->databaseId, self::DATABASE_NAME); - } catch (AppwriteException $err) { - // Silence error if database is already present - if ($err->getType() !== 'database_already_exists') { - throw $err; - } - } - - try { - $this->tablesDB->createTable($this->databaseId, self::TABLE_ID, self::TABLE_NAME); - } catch (AppwriteException $err) { - // Silence error if table is already present - if ($err->getType() !== 'table_already_exists') { - throw $err; - } - } - - try { - $this->tablesDB->createStringColumn($this->databaseId, self::TABLE_ID, 'key', 255, true); - } catch (AppwriteException $err) { - // Silence error if column is already present - if ($err->getType() !== 'column_already_exists') { - throw $err; - } } - try { - $this->tablesDB->createDatetimeColumn($this->databaseId, self::TABLE_ID, 'time', true); - } catch (AppwriteException $err) { - // Silence error if column is already present - if ($err->getType() !== 'column_already_exists') { - throw $err; - } - } + $this->createDatabase(); + $this->createTable(); + $this->createColumns(); + $this->waitForResourcesReady('columns'); + $this->createIndexes(); + $this->waitForResourcesReady('indexes'); + $this->createLockTable(); + } + protected function isSetupComplete(): bool + { try { - $this->tablesDB->createIntegerColumn($this->databaseId, self::TABLE_ID, 'count', true, 0, PHP_INT_MAX); - } catch (AppwriteException $err) { - // Silence error if column is already present - if ($err->getType() !== 'column_already_exists') { - throw $err; - } + $this->tablesDB->getTable($this->databaseId, self::TABLE_LOCK); + return true; + } catch (\Throwable $err) { + return false; } + } - // Await till all attributes are ready - $ready = false; - $attempts = 0; - while ($attempts < 15) { - $attempts++; - - $response = $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [ - Query::notEqual('status', 'available'), - Query::limit(1) - ]); - - $columns = $response['columns']; - - // Temporary fix due to bug in Appwrite listColumns queries - $columns = \array_filter($columns, fn ($column) => $column['status'] !== 'available'); - - if (\count($columns) === 0) { - $ready = true; - break; - } + protected function createDatabase(): void + { + $this->executeWithSilentError( + fn () => $this->tablesDB->create($this->databaseId, self::DATABASE_NAME), + 'database_already_exists' + ); + } - \sleep(1); - } + protected function createTable(): void + { + $this->executeWithSilentError( + fn () => $this->tablesDB->createTable($this->databaseId, self::TABLE_ID, self::TABLE_NAME), + 'table_already_exists' + ); + } - if (!$ready) { - throw new \Exception('Failed to setup tables.'); + protected function createColumns(): void + { + $columns = [ + fn () => $this->tablesDB->createStringColumn($this->databaseId, self::TABLE_ID, 'key', 255, true), + fn () => $this->tablesDB->createDatetimeColumn($this->databaseId, self::TABLE_ID, 'time', true), + fn () => $this->tablesDB->createIntegerColumn($this->databaseId, self::TABLE_ID, 'count', true, 0, PHP_INT_MAX) + ]; + + foreach ($columns as $createColumnFunction) { + $this->executeWithSilentError($createColumnFunction, 'column_already_exists'); } + } - try { - $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', IndexType::UNIQUE(), ['key', 'time']); - } catch (AppwriteException $err) { - // Silence error if index is already present - if ($err->getType() !== 'index_already_exists') { - throw $err; - } - } + protected function createIndexes(): void + { + $indexes = [ + fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', IndexType::UNIQUE(), ['key', 'time']), + fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', IndexType::KEY(), ['time']) + ]; - try { - $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', IndexType::KEY(), ['time']); - } catch (AppwriteException $err) { - // Silence error if index is already present - if ($err->getType() !== 'index_already_exists') { - throw $err; - } + foreach ($indexes as $createIndexFunction) { + $this->executeWithSilentError($createIndexFunction, 'index_already_exists'); } + } - // Await till all indexes are ready - $ready = false; + protected function waitForResourcesReady(string $resourceType): void + { $attempts = 0; - while ($attempts < 15) { - $attempts++; + $maxAttempts = 15; - $response = $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [ - Query::notEqual('status', 'available'), - Query::limit(1) - ]); + while ($attempts < $maxAttempts) { + $attempts++; - $indexes = $response['indexes']; + $response = $resourceType === 'columns' + ? $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)]) + : $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)]); - // Temporary fix due to bug in Appwrite listColumns queries - $indexes = \array_filter($indexes, fn ($index) => $index['status'] !== 'available'); + $resources = $response[$resourceType]; + $resources = \array_filter($resources, fn ($resource) => $resource['status'] !== 'available'); - if (\count($indexes) === 0) { - $ready = true; - break; + if (\count($resources) === 0) { + return; } \sleep(1); } - if (!$ready) { - throw new \Exception('Failed to setup tables.'); - } + throw new \Exception("Failed to setup {$resourceType}."); + } - // Optimize future setup checks + protected function createLockTable(): void + { + $this->executeWithSilentError( + fn () => $this->tablesDB->createTable($this->databaseId, self::TABLE_LOCK, name: self::TABLE_LOCK), + 'table_already_exists' + ); + } + + protected function executeWithSilentError(callable $callback, string $allowedErrorType): void + { try { - $this->tablesDB->createTable($this->databaseId, self::TABLE_LOCK, name: self::TABLE_LOCK); + $callback(); } catch (AppwriteException $err) { - // Silence error if table is already present - if ($err->getType() !== 'table_already_exists') { + if ($err->getType() !== $allowedErrorType) { throw $err; } } From b1a231960864b6773f15c35e9849c3deb47cd9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Nov 2025 22:20:01 +0100 Subject: [PATCH 319/470] Update adapter namespace --- .env.example | 3 +++ .github/workflows/tests.yml | 6 ++++++ .gitignore | 3 ++- docker-compose.yml | 4 ++++ .../{AppwriteTablesDB.php => Appwrite/TablesDB.php} | 10 +++++----- .../TablesDBTest.php} | 12 ++++++------ 6 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 .env.example rename src/Abuse/Adapters/TimeLimit/{AppwriteTablesDB.php => Appwrite/TablesDB.php} (97%) rename tests/Abuse/{AppwriteTablesDBTest.php => Appwrite/TablesDBTest.php} (51%) diff --git a/.env.example b/.env.example new file mode 100644 index 00000000000..013b71041f3 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +APPWRITE_ENDPOINT= +APPWRITE_PROJECT_ID= +APPWRITE_API_KEY= \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 880da7f6936..c17c12fbc02 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,12 @@ jobs: - run: git checkout HEAD^2 + - name: Generate .env file + run: | + echo "APPWRITE_ENDPOINT=${{ secrets.APPWRITE_ENDPOINT }}" > .env + echo "APPWRITE_PROJECT_ID=${{ secrets.APPWRITE_PROJECT_ID }}" >> .env + echo "APPWRITE_API_KEY=${{ secrets.APPWRITE_API_KEY }}" >> .env + - name: Build run: | export PHP_VERSION=${{ matrix.php-versions }} diff --git a/.gitignore b/.gitignore index e244eda0bc4..0ec0248901f 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ -/.idea/ \ No newline at end of file +/.idea/ +.env \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index f3a0998b7fb..d1345c860f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,10 @@ services: depends_on: - redis - mysql + environment: + - APPWRITE_ENDPOINT + - APPWRITE_PROJECT_ID + - APPWRITE_API_KEY volumes: - ./phpunit.xml:/code/phpunit.xml - ./src:/code/src diff --git a/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php similarity index 97% rename from src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php rename to src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 102c65dc293..d731813b09a 100644 --- a/src/Abuse/Adapters/TimeLimit/AppwriteTablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -1,18 +1,18 @@ timestamp = (int)($now - ($now % $seconds)); $this->limit = $limit; - $this->tablesDB = new TablesDB($client); + $this->tablesDB = new TablesDBService($client); $this->databaseId = $databaseId; } diff --git a/tests/Abuse/AppwriteTablesDBTest.php b/tests/Abuse/Appwrite/TablesDBTest.php similarity index 51% rename from tests/Abuse/AppwriteTablesDBTest.php rename to tests/Abuse/Appwrite/TablesDBTest.php index eef531352be..10708c25502 100755 --- a/tests/Abuse/AppwriteTablesDBTest.php +++ b/tests/Abuse/Appwrite/TablesDBTest.php @@ -4,7 +4,7 @@ use Appwrite\Client; use Utopia\Abuse\Adapters\TimeLimit; -use Utopia\Abuse\Adapters\TimeLimit\AppwriteTablesDB as AdapterAppwriteTablesDB; +use Utopia\Abuse\Adapters\TimeLimit\Appwrite\TablesDB; class AppwriteTablesDBTest extends Base { @@ -24,17 +24,17 @@ private static function initialiseDatabase(): void { self::$databaseId = 'abuse-cicd-' . \uniqid(); self::$client = (new Client()) - ->setEndpoint('https://fra.cloud.appwrite.io/v1') - ->setProject('68f0b93e003404ce2e31') // Utopia PHP - ->setKey('standard_6e7fe493f9dbe734c77eb701982baa223bc95149b61496306ce9e03276e0f79112cd8738a178d78cc5c66f0ae8ad912a71260086f1ab6b5c18271be3c58f66c9b5e3cca22a470a220093c585a5c5b24831c3fdac6ee8fdda7b19a5a63316bf45cbb30d9bc7e84a5e2580fac24acb6273fd13d86e2b2cf830276df9afd43f59f2'); + ->setEndpoint(\getenv('APPWRITE_ENDPOINT')) + ->setProject(\getenv('APPWRITE_PROJECT_ID')) + ->setKey(\getenv('APPWRITE_API_KEY')); - $adapter = new AdapterAppwriteTablesDB('', 1, 1, self::$client, self::$databaseId); + $adapter = new TablesDB('', 1, 1, self::$client, self::$databaseId); $adapter->setup(); } public function getAdapter(string $key, int $limit, int $seconds): TimeLimit { - return new AdapterAppwriteTablesDB($key, $limit, $seconds, self::$client, self::$databaseId); + return new TablesDB($key, $limit, $seconds, self::$client, self::$databaseId); } public static function tearDownAfterClass(): void From 25e4c022cfc92b6e1f3897d32c4bbd316e1a4109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Nov 2025 22:26:37 +0100 Subject: [PATCH 320/470] Update docs --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index e17e4ce7b43..401640485eb 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ composer require utopia-php/abuse The time limit abuse allow each key (action) to be performed [X] times in given time frame. This adapter uses a MySQL / MariaDB to store usage attempts. Before using it create the table schema as documented in this repository (./data/schema.sql) +### Database adapter + ```php check()) { } ``` +### Appwrite TablesDB adapter + +```php +setEndpoint('[YOUR_ENDPOINT]') + ->setProject('[YOUR_PROJECT_ID]') + ->setKey('[YOUR_API_KEY]'); +$databaseId = 'abuse'; + +// Limit login attempts to 10 time in 5 minutes time frame +$adapter = new TablesDBAdapter('login-attempt-from-{{ip}}', 10, (60 * 5), $client, $databaseId); + +$adapter->setup(); //setup database as required +$adapter->setParam('{{ip}}', '127.0.0.1'); + +$abuse = new Abuse($adapter); + +// Use vars to resolve adapter key + +if($abuse->check()) { + throw new Exception('Service was abused!'); // throw error and return X-Rate limit headers here +} +``` + **ReCaptcha Abuse** The ReCaptcha abuse controller is using Google ReCaptcha service to detect when service is being abused by bots. From 6520245f007f93d937b442f540775059c11a29d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 13 Nov 2025 22:27:02 +0100 Subject: [PATCH 321/470] Fix linter --- tests/Abuse/Appwrite/TablesDBTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Abuse/Appwrite/TablesDBTest.php b/tests/Abuse/Appwrite/TablesDBTest.php index 10708c25502..f45897dfc8f 100755 --- a/tests/Abuse/Appwrite/TablesDBTest.php +++ b/tests/Abuse/Appwrite/TablesDBTest.php @@ -24,9 +24,9 @@ private static function initialiseDatabase(): void { self::$databaseId = 'abuse-cicd-' . \uniqid(); self::$client = (new Client()) - ->setEndpoint(\getenv('APPWRITE_ENDPOINT')) - ->setProject(\getenv('APPWRITE_PROJECT_ID')) - ->setKey(\getenv('APPWRITE_API_KEY')); + ->setEndpoint(\getenv('APPWRITE_ENDPOINT') ?: '') + ->setProject(\getenv('APPWRITE_PROJECT_ID') ?: '') + ->setKey(\getenv('APPWRITE_API_KEY') ?: ''); $adapter = new TablesDB('', 1, 1, self::$client, self::$databaseId); $adapter->setup(); From 96b8499af0d0c3c3fef9b8456bae62322da78dd7 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 26 Nov 2025 15:36:46 +0545 Subject: [PATCH 322/470] Refactor: assertions - replace assertEquals with assertSame --- tests/Abuse/Base.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index f0bc33709f7..81a4d5e4e22 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -17,9 +17,9 @@ public function testStaticKey(): void { $adapter = $this->getAdapter('static-key', 2, 1); $abuse = new Abuse($adapter); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), true); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), true); } /** @@ -30,9 +30,9 @@ public function testDynamicKey(): void $adapter = $this->getAdapter('dynamic-key-{{ip}}', 2, 1); $adapter->setParam('{{ip}}', '0.0.0.10'); $abuse = new Abuse($adapter); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), true); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), true); } /** @@ -44,9 +44,9 @@ public function testDynamicKeyWith2Params(): void $adapter->setParam('{{ip}}', '0.0.0.10'); $adapter->setParam('{{email}}', 'test@test.com'); $abuse = new Abuse($adapter); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), false); - $this->assertEquals($abuse->check(), true); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), true); } /** @@ -58,9 +58,9 @@ public function testDynamicKeyFastRequests(): void $adapter->setParam('{{ip}}', '0.0.0.10'); $abuse = new Abuse($adapter); for ($i = 0; $i < 10; $i++) { - $this->assertEquals($abuse->check(), false); + $this->assertSame($abuse->check(), false); } - $this->assertEquals($abuse->check(), true); + $this->assertSame($abuse->check(), true); } /** @@ -72,9 +72,9 @@ public function testLimitReset(): void $adapter->setParam('{{ip}}', '127.0.0.1'); $abuse = new Abuse($adapter); for ($i = 0; $i < 10; $i++) { - $this->assertEquals($abuse->check(), false); + $this->assertSame($abuse->check(), false); } - $this->assertEquals($abuse->check(), true); + $this->assertSame($abuse->check(), true); // Wait for the limit to reset sleep(2); @@ -83,7 +83,7 @@ public function testLimitReset(): void $adapter = $this->getAdapter('limit-reset-{{ip}}', 10, 1); $adapter->setParam('{{ip}}', '127.0.0.1'); $abuse = new Abuse($adapter); - $this->assertEquals($abuse->check(), false); + $this->assertSame($abuse->check(), false); } /** @@ -93,7 +93,7 @@ public function testTimeFormat(): void { $now = time(); $adapter = $this->getAdapter('', 1, 1); - $this->assertEquals($adapter->time(), $now); - $this->assertEquals(true, \is_int($adapter->time())); + $this->assertSame($adapter->time(), $now); + $this->assertSame(true, \is_int($adapter->time())); } } From 8a72773e9fb3e887e2a206e64df7e0fa83110186 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:54:09 +0000 Subject: [PATCH 323/470] Initial plan From 14ccf84d23df4d15c6b48ef88b8dcf0728bc38c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:59:31 +0000 Subject: [PATCH 324/470] Implement reset() method for Abuse interface and adapters Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com> --- src/Abuse/Abuse.php | 12 ++++ src/Abuse/Adapter.php | 9 +++ src/Abuse/Adapters/ReCaptcha.php | 14 +++++ src/Abuse/Adapters/TimeLimit.php | 16 +++++ .../Adapters/TimeLimit/Appwrite/TablesDB.php | 58 ++++++++++++++++++ src/Abuse/Adapters/TimeLimit/Database.php | 59 +++++++++++++++++++ src/Abuse/Adapters/TimeLimit/Redis.php | 21 +++++++ src/Abuse/Adapters/TimeLimit/RedisCluster.php | 24 ++++++++ tests/Abuse/Base.php | 29 +++++++++ 9 files changed, 242 insertions(+) diff --git a/src/Abuse/Abuse.php b/src/Abuse/Abuse.php index 948939913d3..cdb6f9c54b2 100644 --- a/src/Abuse/Abuse.php +++ b/src/Abuse/Abuse.php @@ -53,4 +53,16 @@ public function cleanup(int $timestamp): bool { return $this->adapter->cleanup($timestamp); } + + /** + * Reset + * + * Reset the count to 0 for the current adapter + * + * @return void + */ + public function reset(): void + { + $this->adapter->reset(); + } } diff --git a/src/Abuse/Adapter.php b/src/Abuse/Adapter.php index 7de89678700..b511d0319b2 100644 --- a/src/Abuse/Adapter.php +++ b/src/Abuse/Adapter.php @@ -83,4 +83,13 @@ abstract public function getLogs(?int $offset = null, ?int $limit = 25): array; * @return bool */ abstract public function cleanup(int $timestamp): bool; + + /** + * Reset + * + * Reset the count to 0 + * + * @return void + */ + abstract public function reset(): void; } diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 15ecbdda84e..0a8120228df 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -117,4 +117,18 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array { throw new Exception('Method not supported'); } + + /** + * Reset + * + * Reset is not applicable for ReCaptcha adapter + * + * @return void + * + * @throws Exception + */ + public function reset(): void + { + throw new Exception('Method not supported'); + } } diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index d0bd655a7a9..073c8f3dff8 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -37,6 +37,8 @@ abstract protected function count(string $key, int $timestamp): int; abstract protected function hit(string $key, int $timestamp): void; + abstract protected function set(string $key, int $timestamp, int $value): void; + /** * Check * @@ -102,4 +104,18 @@ public function time(): int { return $this->timestamp; } + + /** + * Reset + * + * Reset the count to 0 for the current key and timestamp + * + * @return void + * + * @throws \Exception + */ + public function reset(): void + { + $this->set($this->parseKey(), $this->timestamp, 0); + } } diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index d731813b09a..84223a0ad0d 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -250,6 +250,64 @@ protected function hit(string $key, int $timestamp): void $this->count++; } + /** + * @param string $key + * @param int $timestamp + * @param int $value + * @return void + * + * @throws \Throwable + */ + protected function set(string $key, int $timestamp, int $value): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $timestamp = $this->toDateTime($timestamp); + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + $data = $rows[0] ?? null; + + if (\is_null($data)) { + $data = [ + 'key' => $key, + 'time' => $timestamp, + 'count' => $value, + ]; + + try { + $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); + } catch (AppwriteException $err) { + if ($err->getType() !== 'row_already_exists') { + throw $err; + } + + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + + $data = $rows[0] ?? null; + + if (!is_null($data)) { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + } else { + throw new \Exception('Document Not Found'); + } + } + } else { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + } + + $this->count = $value; + } + /** * Get abuse logs * diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index ae36f01cd0d..19539c07670 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -215,6 +215,65 @@ protected function hit(string $key, int $timestamp): void $this->count++; } + /** + * @param string $key + * @param int $timestamp + * @param int $value + * @return void + * + * @throws AuthorizationException|Structure|\Exception|\Throwable + */ + protected function set(string $key, int $timestamp, int $value): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $timestamp = $this->toDateTime($timestamp); + Authorization::skip(function () use ($timestamp, $key, $value) { + $data = $this->db->findOne(self::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + + if ($data->isEmpty()) { + $data = [ + '$permissions' => [], + 'key' => $key, + 'time' => $timestamp, + 'count' => $value, + '$collection' => self::COLLECTION, + ]; + + try { + $this->db->createDocument(self::COLLECTION, new Document($data)); + } catch (Duplicate $e) { + // Duplicate in case of race condition - update existing document + $data = $this->db->findOne(self::COLLECTION, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + + if (!$data->isEmpty()) { + /** @var Document $data */ + $this->db->updateDocument(self::COLLECTION, $data->getId(), new Document([ + 'count' => $value, + ])); + } else { + throw new \Exception('Document Not Found'); + } + } + } else { + /** @var Document $data */ + $this->db->updateDocument(self::COLLECTION, $data->getId(), new Document([ + 'count' => $value, + ])); + } + }); + + $this->count = $value; + } + /** * Get abuse logs * diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 19602613a2f..139f742de5d 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -77,6 +77,27 @@ protected function hit(string $key, int $timestamp): void $this->count = ($this->count ?? 0) + 1; } + /** + * @param string $key + * @param int $timestamp + * @param int $value + * @return void + */ + protected function set(string $key, int $timestamp, int $value): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; + $this->redis->multi() + ->set($key, (string)$value) + ->expire($key, $this->ttl) + ->exec(); + + $this->count = $value; + } + /** * Get abuse logs * diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index 6acc99a8f41..ed3d1bca456 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -79,6 +79,30 @@ protected function hit(string $key, int $timestamp): void $this->count = ($this->count ?? 0) + 1; } + /** + * Set count for a key at specific timestamp + * + * @param string $key + * @param int $timestamp + * @param int $value + * @return void + */ + protected function set(string $key, int $timestamp, int $value): void + { + if (0 == $this->limit) { // No limit no point for counting + return; + } + + $key = self::NAMESPACE . '__'. $key .'__'. $timestamp; + + $this->redis->multi(); + $this->redis->set($key, (string)$value); + $this->redis->expire($key, $this->ttl); + $this->redis->exec(); + + $this->count = $value; + } + /** * Get abuse logs with proper cursor-based pagination * diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 81a4d5e4e22..6aefd41f4af 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -96,4 +96,33 @@ public function testTimeFormat(): void $this->assertSame($adapter->time(), $now); $this->assertSame(true, \is_int($adapter->time())); } + + /** + * Test the reset functionality + */ + public function testReset(): void + { + $adapter = $this->getAdapter('reset-test-{{ip}}', 5, 1); + $adapter->setParam('{{ip}}', '192.168.1.1'); + $abuse = new Abuse($adapter); + + // Make 3 checks + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + + // Reset the count + $abuse->reset(); + + // Should be able to make 5 checks again + $adapter = $this->getAdapter('reset-test-{{ip}}', 5, 1); + $adapter->setParam('{{ip}}', '192.168.1.1'); + $abuse = new Abuse($adapter); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), true); + } } From 2694a0a7692917b10de8051cfaaa0bc77a33d8ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:03:22 +0000 Subject: [PATCH 325/470] Improve error messages in Database and TablesDB adapters Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com> --- src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php | 2 +- src/Abuse/Adapters/TimeLimit/Database.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 84223a0ad0d..89db9616966 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -298,7 +298,7 @@ protected function set(string $key, int $timestamp, int $value): void if (!is_null($data)) { $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); } else { - throw new \Exception('Document Not Found'); + throw new \Exception('Unable to find abuse tracking row after race condition handling'); } } } else { diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 19539c07670..2e7648830ad 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -260,7 +260,7 @@ protected function set(string $key, int $timestamp, int $value): void 'count' => $value, ])); } else { - throw new \Exception('Document Not Found'); + throw new \Exception('Unable to find abuse tracking document after race condition handling'); } } } else { From f9ed0685c92885df3ed4b78b3a187e2472fa8351 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:04:30 +0000 Subject: [PATCH 326/470] Fix docblock and improve string concatenation consistency Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com> --- src/Abuse/Adapters/TimeLimit/Redis.php | 2 ++ src/Abuse/Adapters/TimeLimit/RedisCluster.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 139f742de5d..8db3e299502 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -78,6 +78,8 @@ protected function hit(string $key, int $timestamp): void } /** + * Set count for a key at specific timestamp + * * @param string $key * @param int $timestamp * @param int $value diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index ed3d1bca456..abb6379030b 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -93,7 +93,7 @@ protected function set(string $key, int $timestamp, int $value): void return; } - $key = self::NAMESPACE . '__'. $key .'__'. $timestamp; + $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; $this->redis->multi(); $this->redis->set($key, (string)$value); From c5bfa4d6cd1b941202f41e049ef44019afac2aa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:16:22 +0000 Subject: [PATCH 327/470] Remove limit check from set() methods and simplify TablesDB logic Co-authored-by: Meldiron <19310830+Meldiron@users.noreply.github.com> --- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 57 +++++++------------ src/Abuse/Adapters/TimeLimit/Database.php | 4 -- src/Abuse/Adapters/TimeLimit/Redis.php | 4 -- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 4 -- 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 89db9616966..94ad05ad61e 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -260,49 +260,34 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } - $timestamp = $this->toDateTime($timestamp); - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ - Query::equal('key', [$key]), - Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; - $data = $rows[0] ?? null; - - if (\is_null($data)) { - $data = [ - 'key' => $key, - 'time' => $timestamp, - 'count' => $value, - ]; + $data = [ + 'key' => $key, + 'time' => $timestamp, + 'count' => $value, + ]; - try { - $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); - } catch (AppwriteException $err) { - if ($err->getType() !== 'row_already_exists') { - throw $err; - } + try { + $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); + } catch (AppwriteException $err) { + if ($err->getType() !== 'row_already_exists') { + throw $err; + } - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ - Query::equal('key', [$key]), - Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; - $data = $rows[0] ?? null; + $data = $rows[0] ?? null; - if (!is_null($data)) { - $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); - } else { - throw new \Exception('Unable to find abuse tracking row after race condition handling'); - } + if (!is_null($data)) { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + } else { + throw new \Exception('Unable to find abuse tracking row after race condition handling'); } - } else { - $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); } $this->count = $value; diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 2e7648830ad..9fa8a4c478f 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -225,10 +225,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } - $timestamp = $this->toDateTime($timestamp); Authorization::skip(function () use ($timestamp, $key, $value) { $data = $this->db->findOne(self::COLLECTION, [ diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 8db3e299502..2ffb02090ba 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -87,10 +87,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } - $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; $this->redis->multi() ->set($key, (string)$value) diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index abb6379030b..12ec73e06b7 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -89,10 +89,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } - $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; $this->redis->multi(); From b51d34f8da72bbe08470b345ec4b8fbf8fa2da49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 5 Jan 2026 22:20:20 +0100 Subject: [PATCH 328/470] Improve PR quality --- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 4 --- src/Abuse/Adapters/TimeLimit/Database.php | 3 -- src/Abuse/Adapters/TimeLimit/Redis.php | 3 -- src/Abuse/Adapters/TimeLimit/RedisCluster.php | 3 -- tests/Abuse/Base.php | 28 +++++++++++++------ 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 89db9616966..be0fa12e6cc 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -260,10 +260,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } - $timestamp = $this->toDateTime($timestamp); $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 2e7648830ad..a2eb69726ba 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -225,9 +225,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } $timestamp = $this->toDateTime($timestamp); Authorization::skip(function () use ($timestamp, $key, $value) { diff --git a/src/Abuse/Adapters/TimeLimit/Redis.php b/src/Abuse/Adapters/TimeLimit/Redis.php index 8db3e299502..e6265735680 100644 --- a/src/Abuse/Adapters/TimeLimit/Redis.php +++ b/src/Abuse/Adapters/TimeLimit/Redis.php @@ -87,9 +87,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; $this->redis->multi() diff --git a/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/src/Abuse/Adapters/TimeLimit/RedisCluster.php index abb6379030b..731415e9ae0 100644 --- a/src/Abuse/Adapters/TimeLimit/RedisCluster.php +++ b/src/Abuse/Adapters/TimeLimit/RedisCluster.php @@ -89,9 +89,6 @@ protected function hit(string $key, int $timestamp): void */ protected function set(string $key, int $timestamp, int $value): void { - if (0 == $this->limit) { // No limit no point for counting - return; - } $key = self::NAMESPACE . '__' . $key . '__' . $timestamp; diff --git a/tests/Abuse/Base.php b/tests/Abuse/Base.php index 6aefd41f4af..fcdb115c99a 100644 --- a/tests/Abuse/Base.php +++ b/tests/Abuse/Base.php @@ -102,27 +102,37 @@ public function testTimeFormat(): void */ public function testReset(): void { - $adapter = $this->getAdapter('reset-test-{{ip}}', 5, 1); + $adapter = $this->getAdapter('reset-test-{{ip}}', 5, 600); $adapter->setParam('{{ip}}', '192.168.1.1'); $abuse = new Abuse($adapter); - - // Make 3 checks + + // 5 OK, 6th has limit + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); - + $this->assertSame($abuse->check(), true); + // Reset the count $abuse->reset(); - - // Should be able to make 5 checks again - $adapter = $this->getAdapter('reset-test-{{ip}}', 5, 1); - $adapter->setParam('{{ip}}', '192.168.1.1'); - $abuse = new Abuse($adapter); + + // Should be 5 more OK, then 6th limit $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), false); $this->assertSame($abuse->check(), true); + + // TO be sure, lets do bunch of requests with resets + // All should pass successfully + $adapter = $this->getAdapter('reset-test-{{ip}}', 2, 600); + $adapter->setParam('{{ip}}', '192.168.1.2'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 15; $i++) { + $this->assertSame($abuse->check(), false); + $abuse->reset(); + } } } From 31e42252e68bdd4ded65339022e70845df60ef01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 5 Jan 2026 22:21:51 +0100 Subject: [PATCH 329/470] Revert changes --- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 94ad05ad61e..be0fa12e6cc 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -262,32 +262,43 @@ protected function set(string $key, int $timestamp, int $value): void { $timestamp = $this->toDateTime($timestamp); - $data = [ - 'key' => $key, - 'time' => $timestamp, - 'count' => $value, - ]; + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + $data = $rows[0] ?? null; - try { - $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); - } catch (AppwriteException $err) { - if ($err->getType() !== 'row_already_exists') { - throw $err; - } + if (\is_null($data)) { + $data = [ + 'key' => $key, + 'time' => $timestamp, + 'count' => $value, + ]; - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ - Query::equal('key', [$key]), - Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; + try { + $this->tablesDB->createRow($this->databaseId, self::TABLE_ID, ID::unique(), $data); + } catch (AppwriteException $err) { + if ($err->getType() !== 'row_already_exists') { + throw $err; + } - $data = $rows[0] ?? null; + $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + Query::equal('key', [$key]), + Query::equal('time', [$timestamp]), + ]); + $rows = $response['rows']; + + $data = $rows[0] ?? null; - if (!is_null($data)) { - $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); - } else { - throw new \Exception('Unable to find abuse tracking row after race condition handling'); + if (!is_null($data)) { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + } else { + throw new \Exception('Unable to find abuse tracking row after race condition handling'); + } } + } else { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); } $this->count = $value; From 7bece2fd2ac0726e2322fc761b300bd7de0a469f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 5 Jan 2026 22:27:13 +0100 Subject: [PATCH 330/470] Upgrade Appwrite SDK --- composer.json | 2 +- composer.lock | 442 +++++++++++++++++++++++++++----------------------- 2 files changed, 242 insertions(+), 202 deletions(-) diff --git a/composer.json b/composer.json index 1d2c9101499..5f9235d32bf 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "3.*.*", - "appwrite/appwrite": "18.*.*" + "appwrite/appwrite": "19.*.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 3dc63a55cef..55995657f84 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "835e092b19aef86b434e86b94fbf39ca", + "content-hash": "77897ae78f63709875ab922685547528", "packages": [ { "name": "appwrite/appwrite", - "version": "18.0.1", + "version": "19.1.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03" + "reference": "8738e812062f899c85b2598eef43d6a247f08a56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/950112a73c05297e23fa2eddb68f4ce8a6024e03", - "reference": "950112a73c05297e23fa2eddb68f4ce8a6024e03", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/8738e812062f899c85b2598eef43d6a247f08a56", + "reference": "8738e812062f899c85b2598eef43d6a247f08a56", "shasum": "" }, "require": { @@ -43,23 +43,23 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/18.0.1", + "source": "https://github.com/appwrite/sdk-for-php/tree/19.1.0", "url": "https://appwrite.io/support" }, - "time": "2025-11-10T12:50:46+00:00" + "time": "2025-12-18T08:07:43+00:00" }, { "name": "brick/math", - "version": "0.14.0", + "version": "0.14.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", - "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", + "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", "shasum": "" }, "require": { @@ -98,7 +98,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.0" + "source": "https://github.com/brick/math/tree/0.14.1" }, "funding": [ { @@ -106,7 +106,7 @@ "type": "github" } ], - "time": "2025-08-29T12:40:03+00:00" + "time": "2025-11-24T14:40:29+00:00" }, { "name": "composer/semver", @@ -187,16 +187,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.1", + "version": "v4.33.2", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12" + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12", - "reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", "shasum": "" }, "require": { @@ -225,22 +225,22 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" }, - "time": "2025-11-12T21:58:05+00:00" + "time": "2025-12-05T22:12:22+00:00" }, { "name": "mongodb/mongodb", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "f399d24905dd42f97dfe0af9706129743ef247ac" + "reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac", - "reference": "f399d24905dd42f97dfe0af9706129743ef247ac", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/0a2472ba9cbb932f7e43a8770aedb2fc30612a67", + "reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67", "shasum": "" }, "require": { @@ -256,7 +256,7 @@ "require-dev": { "doctrine/coding-standard": "^12.0", "phpunit/phpunit": "^10.5.35", - "rector/rector": "^1.2", + "rector/rector": "^2.1.4", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "6.5.*" }, @@ -302,9 +302,9 @@ ], "support": { "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1" + "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.2" }, - "time": "2025-08-13T20:50:05+00:00" + "time": "2025-10-06T12:12:40+00:00" }, { "name": "nyholm/psr7", @@ -452,16 +452,16 @@ }, { "name": "open-telemetry/api", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522" + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/610b79ad9d6d97e8368bcb6c4d42394fbb87b522", - "reference": "610b79ad9d6d97e8368bcb6c4d42394fbb87b522", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", "shasum": "" }, "require": { @@ -481,7 +481,7 @@ ] }, "branch-alias": { - "dev-main": "1.7.x-dev" + "dev-main": "1.8.x-dev" } }, "autoload": { @@ -514,11 +514,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-10-02T23:44:28+00:00" + "time": "2025-10-19T10:49:48+00:00" }, { "name": "open-telemetry/context", @@ -581,16 +581,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2" + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", - "reference": "196f3a1dbce3b2c0f8110d164232c11ac00ddbb2", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/07b02bc71838463f6edcc78d3485c04b48fb263d", + "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d", "shasum": "" }, "require": { @@ -637,11 +637,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-06-16T00:24:51+00:00" + "time": "2025-11-13T08:04:37+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -708,16 +708,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.9.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e" + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", - "reference": "8986bcbcbea79cb1ba9e91c1d621541ad63d6b3e", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", "shasum": "" }, "require": { @@ -797,11 +797,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-10-02T23:44:28+00:00" + "time": "2025-11-25T10:59:15+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1280,20 +1280,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.1", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", - "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1352,9 +1352,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "time": "2025-09-04T20:59:21+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1425,16 +1425,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.6", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", - "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", + "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", "shasum": "" }, "require": { @@ -1465,12 +1465,13 @@ "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -1501,7 +1502,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.6" + "source": "https://github.com/symfony/http-client/tree/v7.4.3" }, "funding": [ { @@ -1521,7 +1522,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T17:41:46+00:00" + "time": "2025-12-23T14:50:43+00:00" }, { "name": "symfony/http-client-contracts", @@ -2067,16 +2068,16 @@ }, { "name": "utopia-php/cache", - "version": "0.13.1", + "version": "0.13.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "97220cb3b3822b166ee016d1646e2ae2815dc540" + "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/97220cb3b3822b166ee016d1646e2ae2815dc540", - "reference": "97220cb3b3822b166ee016d1646e2ae2815dc540", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6", + "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6", "shasum": "" }, "require": { @@ -2085,7 +2086,7 @@ "ext-redis": "*", "php": ">=8.0", "utopia-php/pools": "0.8.*", - "utopia-php/telemetry": "0.1.*" + "utopia-php/telemetry": "*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2113,9 +2114,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.13.1" + "source": "https://github.com/utopia-php/cache/tree/0.13.2" }, - "time": "2025-05-09T14:43:52+00:00" + "time": "2025-12-17T08:55:43+00:00" }, { "name": "utopia-php/compression", @@ -2165,16 +2166,16 @@ }, { "name": "utopia-php/database", - "version": "3.4.0", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec" + "reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e10b4faa4f3a3ef30a5f6d76acdb605469924aec", - "reference": "e10b4faa4f3a3ef30a5f6d76acdb605469924aec", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", + "reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", "shasum": "" }, "require": { @@ -2217,34 +2218,35 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/3.4.0" + "source": "https://github.com/utopia-php/database/tree/3.6.1" }, - "time": "2025-11-13T06:34:20+00:00" + "time": "2025-12-16T09:55:41+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.28", + "version": "0.33.35", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "5aaa94d406577b0059ad28c78022606890dc6de0" + "reference": "82b139fb04f30045db51b0d322224f222da32313" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0", - "reference": "5aaa94d406577b0059ad28c78022606890dc6de0", + "url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313", + "reference": "82b139fb04f30045db51b0d322224f222da32313", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.3", "utopia-php/compression": "0.1.*", - "utopia-php/telemetry": "0.1.*" + "utopia-php/telemetry": "0.1.*", + "utopia-php/validators": "0.1.*" }, "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "laravel/pint": "1.*", + "phpbench/phpbench": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "9.*" }, "type": "library", "autoload": { @@ -2264,9 +2266,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.28" + "source": "https://github.com/utopia-php/http/tree/0.33.35" }, - "time": "2025-09-25T10:44:24+00:00" + "time": "2025-12-12T08:33:52+00:00" }, { "name": "utopia-php/mongo", @@ -2331,21 +2333,21 @@ }, { "name": "utopia-php/pools", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d" + "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", - "reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8", + "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8", "shasum": "" }, "require": { - "php": ">=8.3", - "utopia-php/telemetry": "0.1.*" + "php": ">=8.4", + "utopia-php/telemetry": "*" }, "require-dev": { "laravel/pint": "1.*", @@ -2377,9 +2379,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.8.2" + "source": "https://github.com/utopia-php/pools/tree/0.8.3" }, - "time": "2025-04-17T02:04:54+00:00" + "time": "2025-12-17T09:35:18+00:00" }, { "name": "utopia-php/telemetry", @@ -2430,6 +2432,51 @@ "source": "https://github.com/utopia-php/telemetry/tree/0.1.1" }, "time": "2025-03-17T11:57:52+00:00" + }, + { + "name": "utopia-php/validators", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/validators.git", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/5c57d5b6cf964f8981807c1d3ea8df620c869080", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A lightweight collection of reusable validators for Utopia projects", + "keywords": [ + "php", + "utopia", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/utopia-php/validators/issues", + "source": "https://github.com/utopia-php/validators/tree/0.1.0" + }, + "time": "2025-11-18T11:05:46+00:00" } ], "packages-dev": [ @@ -2659,16 +2706,16 @@ }, { "name": "laravel/pint", - "version": "v1.25.1", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9" + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/5016e263f95d97670d71b9a987bd8996ade6d8d9", - "reference": "5016e263f95d97670d71b9a987bd8996ade6d8d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", "shasum": "" }, "require": { @@ -2679,13 +2726,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.87.2", - "illuminate/view": "^11.46.0", - "larastan/larastan": "^3.7.1", - "laravel-zero/framework": "^11.45.0", + "friendsofphp/php-cs-fixer": "^3.92.4", + "illuminate/view": "^12.44.0", + "larastan/larastan": "^3.8.1", + "laravel-zero/framework": "^12.0.4", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.1", - "pestphp/pest": "^2.36.0" + "nunomaduro/termwind": "^2.3.3", + "pestphp/pest": "^3.8.4" }, "bin": [ "builds/pint" @@ -2711,6 +2758,7 @@ "description": "An opinionated code formatter for PHP.", "homepage": "https://laravel.com", "keywords": [ + "dev", "format", "formatter", "lint", @@ -2721,7 +2769,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-09-19T02:57:12+00:00" + "time": "2026-01-05T16:49:17+00:00" }, { "name": "myclabs/deep-copy", @@ -2785,16 +2833,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -2837,9 +2885,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -3482,16 +3530,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.29", + "version": "9.6.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" + "reference": "945d0b7f346a084ce5549e95289962972c4272e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", + "reference": "945d0b7f346a084ce5549e95289962972c4272e5", "shasum": "" }, "require": { @@ -3565,7 +3613,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" }, "funding": [ { @@ -3589,7 +3637,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:29:11+00:00" + "time": "2025-12-06T07:45:52+00:00" }, { "name": "psr/cache", @@ -4717,47 +4765,39 @@ }, { "name": "symfony/console", - "version": "v7.3.6", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4791,7 +4831,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.6" + "source": "https://github.com/symfony/console/tree/v8.0.3" }, "funding": [ { @@ -4811,29 +4851,29 @@ "type": "tidelift" } ], - "time": "2025-11-04T01:21:42+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.6", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4861,7 +4901,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.6" + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" }, "funding": [ { @@ -4881,27 +4921,27 @@ "type": "tidelift" } ], - "time": "2025-11-05T09:52:27+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "symfony/finder", - "version": "v7.3.5", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f" + "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f", + "url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12", + "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4929,7 +4969,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.5" + "source": "https://github.com/symfony/finder/tree/v8.0.3" }, "funding": [ { @@ -4949,24 +4989,24 @@ "type": "tidelift" } ], - "time": "2025-10-15T18:45:57+00:00" + "time": "2025-12-23T14:52:06+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.3", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -5000,7 +5040,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -5020,7 +5060,7 @@ "type": "tidelift" } ], - "time": "2025-08-05T10:16:07+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5274,20 +5314,20 @@ }, { "name": "symfony/process", - "version": "v7.3.4", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" + "reference": "0cbbd88ec836f8757641c651bb995335846abb78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", + "url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78", + "reference": "0cbbd88ec836f8757641c651bb995335846abb78", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -5315,7 +5355,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.4" + "source": "https://github.com/symfony/process/tree/v8.0.3" }, "funding": [ { @@ -5335,38 +5375,38 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-12-19T10:01:18+00:00" }, { "name": "symfony/string", - "version": "v7.3.4", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f96476035142921000338bad71e5247fbc138872" + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", - "reference": "f96476035142921000338bad71e5247fbc138872", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5405,7 +5445,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.4" + "source": "https://github.com/symfony/string/tree/v8.0.1" }, "funding": [ { @@ -5425,20 +5465,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:36:48+00:00" + "time": "2025-12-01T09:13:36+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -5467,7 +5507,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -5475,7 +5515,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "webmozart/glob", @@ -5529,7 +5569,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5538,6 +5578,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } From 33a24ad77addb2aecc30aaa35c6183fe18a389ee Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Jan 2026 14:39:58 +1300 Subject: [PATCH 331/470] Update deps --- composer.json | 4 +-- composer.lock | 77 +++++++++++++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index 5f9235d32bf..ffc70931456 100755 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "3.*.*", - "appwrite/appwrite": "19.*.*" + "utopia-php/database": "4.*", + "appwrite/appwrite": "19.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 55995657f84..0f687d2b8ad 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": "77897ae78f63709875ab922685547528", + "content-hash": "fca63c0aef58e61245c71a861b7169ae", "packages": [ { "name": "appwrite/appwrite", @@ -187,16 +187,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.2", + "version": "v4.33.4", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318" + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", - "reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/22d28025cda0d223a2e48c2e16c5284ecc9f5402", + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402", "shasum": "" }, "require": { @@ -225,9 +225,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.4" }, - "time": "2025-12-05T22:12:22+00:00" + "time": "2026-01-12T17:58:43+00:00" }, { "name": "mongodb/mongodb", @@ -2166,16 +2166,16 @@ }, { "name": "utopia-php/database", - "version": "3.6.1", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7" + "reference": "7b935bb09aeae8aeff5a28f6f2485cef1cc4d898" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", - "reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7b935bb09aeae8aeff5a28f6f2485cef1cc4d898", + "reference": "7b935bb09aeae8aeff5a28f6f2485cef1cc4d898", "shasum": "" }, "require": { @@ -2218,29 +2218,29 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/3.6.1" + "source": "https://github.com/utopia-php/database/tree/4.5.1" }, - "time": "2025-12-16T09:55:41+00:00" + "time": "2026-01-14T12:07:24+00:00" }, { "name": "utopia-php/framework", - "version": "0.33.35", + "version": "0.33.37", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "82b139fb04f30045db51b0d322224f222da32313" + "reference": "30a119d76531d89da9240496940c84fcd9e1758b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313", - "reference": "82b139fb04f30045db51b0d322224f222da32313", + "url": "https://api.github.com/repos/utopia-php/http/zipball/30a119d76531d89da9240496940c84fcd9e1758b", + "reference": "30a119d76531d89da9240496940c84fcd9e1758b", "shasum": "" }, "require": { "php": ">=8.3", "utopia-php/compression": "0.1.*", "utopia-php/telemetry": "0.1.*", - "utopia-php/validators": "0.1.*" + "utopia-php/validators": "0.2.*" }, "require-dev": { "laravel/pint": "1.*", @@ -2266,9 +2266,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.35" + "source": "https://github.com/utopia-php/http/tree/0.33.37" }, - "time": "2025-12-12T08:33:52+00:00" + "time": "2026-01-13T10:10:21+00:00" }, { "name": "utopia-php/mongo", @@ -2435,16 +2435,16 @@ }, { "name": "utopia-php/validators", - "version": "0.1.0", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080" + "reference": "30b6030a5b100fc1dff34506e5053759594b2a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/5c57d5b6cf964f8981807c1d3ea8df620c869080", - "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/30b6030a5b100fc1dff34506e5053759594b2a20", + "reference": "30b6030a5b100fc1dff34506e5053759594b2a20", "shasum": "" }, "require": { @@ -2452,7 +2452,7 @@ }, "require-dev": { "laravel/pint": "1.*", - "phpstan/phpstan": "1.*", + "phpstan/phpstan": "2.*", "phpunit/phpunit": "11.*" }, "type": "library", @@ -2474,9 +2474,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.1.0" + "source": "https://github.com/utopia-php/validators/tree/0.2.0" }, - "time": "2025-11-18T11:05:46+00:00" + "time": "2026-01-13T09:16:51+00:00" } ], "packages-dev": [ @@ -2559,30 +2559,29 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.4" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^14", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -2609,7 +2608,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" }, "funding": [ { @@ -2625,7 +2624,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2026-01-05T06:47:08+00:00" }, { "name": "doctrine/lexer", From 98c6d7bcf8c8241a9e6b508e1ea6667360d2ec58 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Jan 2026 14:42:42 +1300 Subject: [PATCH 332/470] Update authz --- src/Abuse/Adapters/TimeLimit/Database.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 9fa8a4c478f..9978f62f680 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -139,7 +139,7 @@ protected function count(string $key, int $timestamp): int $timestamp = $this->toDateTime($timestamp); /** @var array $result */ - $result = Authorization::skip(function () use ($key, $timestamp) { + $result = $this->db->getAuthorization()->skip(function () use ($key, $timestamp) { return $this->db->find(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), @@ -172,7 +172,7 @@ protected function hit(string $key, int $timestamp): void } $timestamp = $this->toDateTime($timestamp); - Authorization::skip(function () use ($timestamp, $key) { + $this->db->getAuthorization()->skip(function () use ($timestamp, $key) { $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), @@ -226,7 +226,7 @@ protected function hit(string $key, int $timestamp): void protected function set(string $key, int $timestamp, int $value): void { $timestamp = $this->toDateTime($timestamp); - Authorization::skip(function () use ($timestamp, $key, $value) { + $this->db->getAuthorization()->skip(function () use ($timestamp, $key, $value) { $data = $this->db->findOne(self::COLLECTION, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), @@ -284,7 +284,7 @@ protected function set(string $key, int $timestamp, int $value): void public function getLogs(?int $offset = null, ?int $limit = 25): array { /** @var array $results */ - $results = Authorization::skip(function () use ($offset, $limit) { + $results = $this->db->getAuthorization()->skip(function () use ($offset, $limit) { $queries = []; $queries[] = Query::orderDesc(''); @@ -312,7 +312,7 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array public function cleanup(int $timestamp): bool { $timestamp = $this->toDateTime($timestamp); - Authorization::skip(function () use ($timestamp) { + $this->db->getAuthorization()->skip(function () use ($timestamp) { do { $documents = $this->db->find(self::COLLECTION, [ Query::lessThan('time', $timestamp), From b7fc9512e640e8d78cfef284338d1e7b317e78c9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 15 Jan 2026 14:44:42 +1300 Subject: [PATCH 333/470] Lint --- src/Abuse/Adapters/TimeLimit/Database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 9978f62f680..01202fd5749 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -10,7 +10,6 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization; use Utopia\Exception; class Database extends TimeLimit From 251856e88367fb139e914d7aba46467167fe176b Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Fri, 30 Jan 2026 17:46:51 +0000 Subject: [PATCH 334/470] chore: upgrade utopia-php/database to 5.* --- composer.json | 2 +- composer.lock | 137 +++++++++++++++++++++++++------------------------- 2 files changed, 70 insertions(+), 69 deletions(-) diff --git a/composer.json b/composer.json index ffc70931456..cee319b1286 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "4.*", + "utopia-php/database": "5.*", "appwrite/appwrite": "19.*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 0f687d2b8ad..0c2f96a86b7 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": "fca63c0aef58e61245c71a861b7169ae", + "content-hash": "15caf2b907ca6aa2302f025501acf35d", "packages": [ { "name": "appwrite/appwrite", @@ -50,16 +50,16 @@ }, { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.2", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2", + "reference": "55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2", "shasum": "" }, "require": { @@ -98,7 +98,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.2" }, "funding": [ { @@ -106,7 +106,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-01-30T14:03:11+00:00" }, { "name": "composer/semver", @@ -187,16 +187,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.4", + "version": "v4.33.5", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402" + "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/22d28025cda0d223a2e48c2e16c5284ecc9f5402", - "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", + "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", "shasum": "" }, "require": { @@ -225,9 +225,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.4" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.5" }, - "time": "2026-01-12T17:58:43+00:00" + "time": "2026-01-29T20:49:00+00:00" }, { "name": "mongodb/mongodb", @@ -452,16 +452,16 @@ }, { "name": "open-telemetry/api", - "version": "1.7.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4" + "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", - "reference": "45bda7efa8fcdd9bdb0daa2f26c8e31f062f49d4", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/df5197c6fd0ddd8e9883b87de042d9341300e2ad", + "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad", "shasum": "" }, "require": { @@ -471,7 +471,7 @@ "symfony/polyfill-php82": "^1.26" }, "conflict": { - "open-telemetry/sdk": "<=1.0.8" + "open-telemetry/sdk": "<=1.11" }, "type": "library", "extra": { @@ -518,7 +518,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-10-19T10:49:48+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "open-telemetry/context", @@ -581,16 +581,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.3", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d" + "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/07b02bc71838463f6edcc78d3485c04b48fb263d", - "reference": "07b02bc71838463f6edcc78d3485c04b48fb263d", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/62e680d587beb42e5247aa6ecd89ad1ca406e8ca", + "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca", "shasum": "" }, "require": { @@ -641,7 +641,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-11-13T08:04:37+00:00" + "time": "2026-01-15T09:31:34+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -708,16 +708,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.10.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99" + "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", - "reference": "3dfc3d1ad729ec7eb25f1b9a4ae39fe779affa99", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/7f1bd524465c1ca42755a9ef1143ba09913f5be0", + "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0", "shasum": "" }, "require": { @@ -758,7 +758,7 @@ ] }, "branch-alias": { - "dev-main": "1.9.x-dev" + "dev-main": "1.12.x-dev" } }, "autoload": { @@ -801,20 +801,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-11-25T10:59:15+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "open-telemetry/sem-conv", - "version": "1.37.0", + "version": "1.38.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1" + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/8da7ec497c881e39afa6657d72586e27efbd29a1", - "reference": "8da7ec497c881e39afa6657d72586e27efbd29a1", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", + "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", "shasum": "" }, "require": { @@ -854,11 +854,11 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-03T12:08:10+00:00" + "time": "2026-01-21T04:14:03+00:00" }, { "name": "php-http/discovery", @@ -1425,16 +1425,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.3", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616" + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616", - "reference": "d01dfac1e0dc99f18da48b18101c23ce57929616", + "url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f", + "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f", "shasum": "" }, "require": { @@ -1502,7 +1502,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.3" + "source": "https://github.com/symfony/http-client/tree/v7.4.5" }, "funding": [ { @@ -1522,7 +1522,7 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:50:43+00:00" + "time": "2026-01-27T16:16:02+00:00" }, { "name": "symfony/http-client-contracts", @@ -2068,16 +2068,16 @@ }, { "name": "utopia-php/cache", - "version": "0.13.2", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6" + "reference": "7068870c086a6aea16173563a26b93ef3e408439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6", - "reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/7068870c086a6aea16173563a26b93ef3e408439", + "reference": "7068870c086a6aea16173563a26b93ef3e408439", "shasum": "" }, "require": { @@ -2085,7 +2085,7 @@ "ext-memcached": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/pools": "0.8.*", + "utopia-php/pools": "1.*", "utopia-php/telemetry": "*" }, "require-dev": { @@ -2114,9 +2114,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.13.2" + "source": "https://github.com/utopia-php/cache/tree/1.0.0" }, - "time": "2025-12-17T08:55:43+00:00" + "time": "2026-01-28T10:55:44+00:00" }, { "name": "utopia-php/compression", @@ -2166,27 +2166,27 @@ }, { "name": "utopia-php/database", - "version": "4.5.1", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "7b935bb09aeae8aeff5a28f6f2485cef1cc4d898" + "reference": "221794bd7de027f9177cd325209e8162ca2520cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/7b935bb09aeae8aeff5a28f6f2485cef1cc4d898", - "reference": "7b935bb09aeae8aeff5a28f6f2485cef1cc4d898", + "url": "https://api.github.com/repos/utopia-php/database/zipball/221794bd7de027f9177cd325209e8162ca2520cb", + "reference": "221794bd7de027f9177cd325209e8162ca2520cb", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-mongodb": "*", "ext-pdo": "*", - "php": ">=8.1", - "utopia-php/cache": "0.13.*", + "php": ">=8.4", + "utopia-php/cache": "1.0.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.11.*", - "utopia-php/pools": "0.8.*" + "utopia-php/pools": "1.0.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -2218,9 +2218,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/4.5.1" + "source": "https://github.com/utopia-php/database/tree/5.0.0" }, - "time": "2026-01-14T12:07:24+00:00" + "time": "2026-01-30T06:17:53+00:00" }, { "name": "utopia-php/framework", @@ -2333,16 +2333,16 @@ }, { "name": "utopia-php/pools", - "version": "0.8.3", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8" + "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8", - "reference": "ad7d6ba946376e81c603204285ce9a674b6502b8", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", + "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", "shasum": "" }, "require": { @@ -2352,7 +2352,8 @@ "require-dev": { "laravel/pint": "1.*", "phpstan/phpstan": "1.*", - "phpunit/phpunit": "11.*" + "phpunit/phpunit": "11.*", + "swoole/ide-helper": "6.*" }, "type": "library", "autoload": { @@ -2379,9 +2380,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.8.3" + "source": "https://github.com/utopia-php/pools/tree/1.0.2" }, - "time": "2025-12-17T09:35:18+00:00" + "time": "2026-01-28T13:12:36+00:00" }, { "name": "utopia-php/telemetry", @@ -5568,7 +5569,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5577,6 +5578,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" + "platform-dev": [], + "plugin-api-version": "2.6.0" } From 3ee854f37ace820e66d84121ede40d49647e1440 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 29 Apr 2026 15:47:13 +0530 Subject: [PATCH 335/470] Remove deprecated curl_close calls --- src/Abuse/Adapters/ReCaptcha.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 0a8120228df..a8b15c913fb 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -81,7 +81,6 @@ public function check(float $score = 0.5): bool $result = \json_decode((string) \curl_exec($ch), true); //close connection - \curl_close($ch); if ($result['success'] && $result['score'] >= $score) { return true; } else { From d2c834fd5ce8ed81924b08a3435b11072d7747fc Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 29 Apr 2026 15:51:55 +0530 Subject: [PATCH 336/470] Clean up curl_close removal formatting --- src/Abuse/Adapters/ReCaptcha.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Abuse/Adapters/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index a8b15c913fb..7e1f67c7570 100644 --- a/src/Abuse/Adapters/ReCaptcha.php +++ b/src/Abuse/Adapters/ReCaptcha.php @@ -80,7 +80,6 @@ public function check(float $score = 0.5): bool /** @var array $result */ $result = \json_decode((string) \curl_exec($ch), true); - //close connection if ($result['success'] && $result['score'] >= $score) { return true; } else { From 2a1fb7973d848cff0ec75fa69a93bc4d2fbc7ada Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Fri, 8 May 2026 08:04:08 +0100 Subject: [PATCH 337/470] feat: bump appwrite/appwrite to 23.* (typed models, TablesDBIndexType) --- composer.json | 2 +- composer.lock | 577 +++++++++--------- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 75 ++- 3 files changed, 310 insertions(+), 344 deletions(-) diff --git a/composer.json b/composer.json index cee319b1286..77e5d7ae126 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "5.*", - "appwrite/appwrite": "19.*" + "appwrite/appwrite": "23.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 0c2f96a86b7..c960ddd9f2f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "15caf2b907ca6aa2302f025501acf35d", + "content-hash": "2818364d7546938e790828df5ee307d1", "packages": [ { "name": "appwrite/appwrite", - "version": "19.1.0", + "version": "23.0.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "8738e812062f899c85b2598eef43d6a247f08a56" + "reference": "d22b36167931294bec4259f0803aba14537030ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/8738e812062f899c85b2598eef43d6a247f08a56", - "reference": "8738e812062f899c85b2598eef43d6a247f08a56", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/d22b36167931294bec4259f0803aba14537030ec", + "reference": "d22b36167931294bec4259f0803aba14537030ec", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", - "php": ">=7.1.0" + "php": ">=8.2.0" }, "require-dev": { - "mockery/mockery": "^1.6.12", + "mockery/mockery": "1.6.12", "phpunit/phpunit": "^10" }, "type": "library", @@ -39,27 +39,27 @@ "license": [ "BSD-3-Clause" ], - "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API", + "description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API", "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/19.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/23.0.0", "url": "https://appwrite.io/support" }, - "time": "2025-12-18T08:07:43+00:00" + "time": "2026-04-16T12:38:44+00:00" }, { "name": "brick/math", - "version": "0.14.2", + "version": "0.14.8", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2" + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2", - "reference": "55c950aa71a2cabc1d8f2bec1f8a7020bd244aa2", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", "shasum": "" }, "require": { @@ -98,7 +98,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.2" + "source": "https://github.com/brick/math/tree/0.14.8" }, "funding": [ { @@ -106,7 +106,7 @@ "type": "github" } ], - "time": "2026-01-30T14:03:11+00:00" + "time": "2026-02-10T14:33:43+00:00" }, { "name": "composer/semver", @@ -187,23 +187,23 @@ }, { "name": "google/protobuf", - "version": "v4.33.5", + "version": "v4.33.6", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d" + "reference": "84b008c23915ed94536737eae46f41ba3bccfe67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", - "reference": "ebe8010a61b2ae0cff0d246fe1c4d44e9f7dfa6d", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/84b008c23915ed94536737eae46f41ba3bccfe67", + "reference": "84b008c23915ed94536737eae46f41ba3bccfe67", "shasum": "" }, "require": { "php": ">=8.1.0" }, "require-dev": { - "phpunit/phpunit": ">=5.0.0 <8.5.27" + "phpunit/phpunit": ">=10.5.62 <11.0.0" }, "suggest": { "ext-bcmath": "Need to support JSON deserialization" @@ -225,9 +225,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.5" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.6" }, - "time": "2026-01-29T20:49:00+00:00" + "time": "2026-03-18T17:32:05+00:00" }, { "name": "mongodb/mongodb", @@ -452,16 +452,16 @@ }, { "name": "open-telemetry/api", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/api.git", - "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad" + "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/df5197c6fd0ddd8e9883b87de042d9341300e2ad", - "reference": "df5197c6fd0ddd8e9883b87de042d9341300e2ad", + "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/6f8d237ce2c304ca85f31970f788e7f074d147be", + "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be", "shasum": "" }, "require": { @@ -518,20 +518,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-01-21T04:14:03+00:00" + "time": "2026-02-25T13:24:05+00:00" }, { "name": "open-telemetry/context", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/context.git", - "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf" + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/d4c4470b541ce72000d18c339cfee633e4c8e0cf", - "reference": "d4c4470b541ce72000d18c339cfee633e4c8e0cf", + "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07", + "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07", "shasum": "" }, "require": { @@ -573,24 +573,24 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-19T00:05:49+00:00" + "time": "2025-10-19T06:44:33+00:00" }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.4", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca" + "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/62e680d587beb42e5247aa6ecd89ad1ca406e8ca", - "reference": "62e680d587beb42e5247aa6ecd89ad1ca406e8ca", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/283a0d66522f2adc6d8d7debfd7686be91c282be", + "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be", "shasum": "" }, "require": { @@ -641,20 +641,20 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-01-15T09:31:34+00:00" + "time": "2026-02-05T09:44:52+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "673af5b06545b513466081884b47ef15a536edde" + "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/673af5b06545b513466081884b47ef15a536edde", - "reference": "673af5b06545b513466081884b47ef15a536edde", + "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/a229cf161d42001d64c8f21e8f678581fe1c66b9", + "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9", "shasum": "" }, "require": { @@ -700,30 +700,30 @@ ], "support": { "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/php", + "docs": "https://opentelemetry.io/docs/languages/php", "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-09-17T23:10:12+00:00" + "time": "2025-10-19T06:44:33+00:00" }, { "name": "open-telemetry/sdk", - "version": "1.12.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0" + "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/7f1bd524465c1ca42755a9ef1143ba09913f5be0", - "reference": "7f1bd524465c1ca42755a9ef1143ba09913f5be0", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/6e3d0ce93e76555dd5e2f1d19443ff45b990e410", + "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.7", + "open-telemetry/api": "^1.8", "open-telemetry/context": "^1.4", "open-telemetry/sem-conv": "^1.0", "php": "^8.1", @@ -801,7 +801,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2026-01-21T04:14:03+00:00" + "time": "2026-03-21T11:50:01+00:00" }, { "name": "open-telemetry/sem-conv", @@ -1358,16 +1358,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -1380,7 +1380,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1405,7 +1405,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -1416,25 +1416,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.5", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f" + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f", - "reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", "shasum": "" }, "require": { @@ -1502,7 +1506,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.5" + "source": "https://github.com/symfony/http-client/tree/v7.4.9" }, "funding": [ { @@ -1522,20 +1526,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-04-29T13:25:15+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { @@ -1548,7 +1552,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1584,7 +1588,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" }, "funding": [ { @@ -1595,25 +1599,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-03-06T13:17:50+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", + "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", "shasum": "" }, "require": { @@ -1665,7 +1673,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" }, "funding": [ { @@ -1685,20 +1693,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "5d2ed36f7734637dacc025f179698031951b1692" + "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692", - "reference": "5d2ed36f7734637dacc025f179698031951b1692", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/34808efe3e68f69685796f7c253a2f1d8ea9df59", + "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59", "shasum": "" }, "require": { @@ -1745,7 +1753,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.37.0" }, "funding": [ { @@ -1765,20 +1773,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", "shasum": "" }, "require": { @@ -1825,7 +1833,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" }, "funding": [ { @@ -1845,20 +1853,20 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-04-10T17:25:58+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", "shasum": "" }, "require": { @@ -1905,7 +1913,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" }, "funding": [ { @@ -1925,20 +1933,20 @@ "type": "tidelift" } ], - "time": "2025-06-23T16:12:55+00:00" + "time": "2026-04-26T13:10:57+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -1956,7 +1964,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1992,7 +2000,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -2012,7 +2020,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "tbachert/spi", @@ -2068,16 +2076,16 @@ }, { "name": "utopia-php/cache", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "7068870c086a6aea16173563a26b93ef3e408439" + "reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/7068870c086a6aea16173563a26b93ef3e408439", - "reference": "7068870c086a6aea16173563a26b93ef3e408439", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/05ceba981436a4022553f7aaa2a05fa049d0f71c", + "reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c", "shasum": "" }, "require": { @@ -2114,22 +2122,22 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/1.0.0" + "source": "https://github.com/utopia-php/cache/tree/1.0.1" }, - "time": "2026-01-28T10:55:44+00:00" + "time": "2026-03-12T03:39:09+00:00" }, { - "name": "utopia-php/compression", - "version": "0.1.3", + "name": "utopia-php/console", + "version": "0.1.1", "source": { "type": "git", - "url": "https://github.com/utopia-php/compression.git", - "reference": "66f093557ba66d98245e562036182016c7dcfe8a" + "url": "https://github.com/utopia-php/console.git", + "reference": "d298e43960780e6d76e66de1228c75dc81220e3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/compression/zipball/66f093557ba66d98245e562036182016c7dcfe8a", - "reference": "66f093557ba66d98245e562036182016c7dcfe8a", + "url": "https://api.github.com/repos/utopia-php/console/zipball/d298e43960780e6d76e66de1228c75dc81220e3e", + "reference": "d298e43960780e6d76e66de1228c75dc81220e3e", "shasum": "" }, "require": { @@ -2137,56 +2145,60 @@ }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", - "vimeo/psalm": "4.0.1" + "squizlabs/php_codesniffer": "^3.6", + "swoole/ide-helper": "4.8.8" }, "type": "library", "autoload": { "psr-4": { - "Utopia\\Compression\\": "src/Compression" + "Utopia\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A simple Compression library to handle file compression", + "description": "Console helpers for logging, prompting, and executing commands", "keywords": [ - "compression", - "framework", + "cli", + "console", "php", - "upf", + "terminal", "utopia" ], "support": { - "issues": "https://github.com/utopia-php/compression/issues", - "source": "https://github.com/utopia-php/compression/tree/0.1.3" + "issues": "https://github.com/utopia-php/console/issues", + "source": "https://github.com/utopia-php/console/tree/0.1.1" }, - "time": "2025-01-15T15:15:51+00:00" + "time": "2026-02-10T10:20:29+00:00" }, { "name": "utopia-php/database", - "version": "5.0.0", + "version": "5.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "221794bd7de027f9177cd325209e8162ca2520cb" + "reference": "eb35e68f7f90932d5a60bd72e70158ae7a4e0511" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/221794bd7de027f9177cd325209e8162ca2520cb", - "reference": "221794bd7de027f9177cd325209e8162ca2520cb", + "url": "https://api.github.com/repos/utopia-php/database/zipball/eb35e68f7f90932d5a60bd72e70158ae7a4e0511", + "reference": "eb35e68f7f90932d5a60bd72e70158ae7a4e0511", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-mongodb": "*", "ext-pdo": "*", + "ext-redis": "*", "php": ">=8.4", - "utopia-php/cache": "1.0.*", - "utopia-php/framework": "0.33.*", - "utopia-php/mongo": "0.11.*", - "utopia-php/pools": "1.0.*" + "utopia-php/cache": "1.*", + "utopia-php/console": "0.1.*", + "utopia-php/mongo": "1.*", + "utopia-php/pools": "1.*", + "utopia-php/validators": "0.2.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -2196,7 +2208,7 @@ "phpunit/phpunit": "9.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.14.*" + "utopia-php/cli": "0.22.*" }, "type": "library", "autoload": { @@ -2218,70 +2230,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/5.0.0" + "source": "https://github.com/utopia-php/database/tree/5.7.0" }, - "time": "2026-01-30T06:17:53+00:00" - }, - { - "name": "utopia-php/framework", - "version": "0.33.37", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/http.git", - "reference": "30a119d76531d89da9240496940c84fcd9e1758b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/30a119d76531d89da9240496940c84fcd9e1758b", - "reference": "30a119d76531d89da9240496940c84fcd9e1758b", - "shasum": "" - }, - "require": { - "php": ">=8.3", - "utopia-php/compression": "0.1.*", - "utopia-php/telemetry": "0.1.*", - "utopia-php/validators": "0.2.*" - }, - "require-dev": { - "laravel/pint": "1.*", - "phpbench/phpbench": "1.*", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "9.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple, light and advanced PHP framework", - "keywords": [ - "framework", - "php", - "upf" - ], - "support": { - "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.37" - }, - "time": "2026-01-13T10:10:21+00:00" + "time": "2026-05-06T01:04:08+00:00" }, { "name": "utopia-php/mongo", - "version": "0.11.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e" + "reference": "73593682deee4696525a04e26524c1c1226e1530" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/34bc0cda8ea368cde68702a6fffe2c3ac625398e", - "reference": "34bc0cda8ea368cde68702a6fffe2c3ac625398e", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/73593682deee4696525a04e26524c1c1226e1530", + "reference": "73593682deee4696525a04e26524c1c1226e1530", "shasum": "" }, "require": { @@ -2327,22 +2291,22 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/0.11.0" + "source": "https://github.com/utopia-php/mongo/tree/1.1.0" }, - "time": "2025-10-20T11:11:23+00:00" + "time": "2026-04-24T06:15:10+00:00" }, { "name": "utopia-php/pools", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1" + "reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", - "reference": "b7d8dd00306cdd8bf3ff6f1dc90caeaf27dabeb1", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/74de7c5457a2c447f27e7ec4d72e8412a7d68c10", + "reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10", "shasum": "" }, "require": { @@ -2380,38 +2344,43 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/1.0.2" + "source": "https://github.com/utopia-php/pools/tree/1.0.3" }, - "time": "2026-01-28T13:12:36+00:00" + "time": "2026-02-26T08:42:40+00:00" }, { "name": "utopia-php/telemetry", - "version": "0.1.1", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f" + "reference": "62bbadad03e593b071b8ca63fac2c117c1900991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/437f0021777f0e575dfb9e8a1a081b3aed75e33f", - "reference": "437f0021777f0e575dfb9e8a1a081b3aed75e33f", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/62bbadad03e593b071b8ca63fac2c117c1900991", + "reference": "62bbadad03e593b071b8ca63fac2c117c1900991", "shasum": "" }, "require": { "ext-opentelemetry": "*", "ext-protobuf": "*", - "nyholm/psr7": "^1.8", - "open-telemetry/exporter-otlp": "^1.1", - "open-telemetry/sdk": "^1.1", + "nyholm/psr7": "1.*", + "open-telemetry/exporter-otlp": "1.*", + "open-telemetry/sdk": "1.*", "php": ">=8.0", - "symfony/http-client": "^7.1" + "symfony/http-client": "7.*" }, "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "laravel/pint": "1.*", + "phpbench/phpbench": "1.*", + "phpstan/phpstan": "2.*", + "phpunit/phpunit": "11.*", + "swoole/ide-helper": "6.*" + }, + "suggest": { + "ext-sockets": "Required for the Swoole transport implementation", + "ext-swoole": "Required for the Swoole transport implementation" }, "type": "library", "autoload": { @@ -2430,22 +2399,22 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.1.1" + "source": "https://github.com/utopia-php/telemetry/tree/0.3.0" }, - "time": "2025-03-17T11:57:52+00:00" + "time": "2026-04-01T13:52:56+00:00" }, { "name": "utopia-php/validators", - "version": "0.2.0", + "version": "0.2.2", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "30b6030a5b100fc1dff34506e5053759594b2a20" + "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/30b6030a5b100fc1dff34506e5053759594b2a20", - "reference": "30b6030a5b100fc1dff34506e5053759594b2a20", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", + "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", "shasum": "" }, "require": { @@ -2475,9 +2444,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.2.0" + "source": "https://github.com/utopia-php/validators/tree/0.2.2" }, - "time": "2026-01-13T09:16:51+00:00" + "time": "2026-04-27T16:30:24+00:00" } ], "packages-dev": [ @@ -2706,16 +2675,16 @@ }, { "name": "laravel/pint", - "version": "v1.27.0", + "version": "v1.29.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90" + "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90", - "reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90", + "url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80", + "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80", "shasum": "" }, "require": { @@ -2726,13 +2695,14 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.92.4", - "illuminate/view": "^12.44.0", - "larastan/larastan": "^3.8.1", - "laravel-zero/framework": "^12.0.4", + "friendsofphp/php-cs-fixer": "^3.95.1", + "illuminate/view": "^12.56.0", + "larastan/larastan": "^3.9.6", + "laravel-zero/framework": "^12.1.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.4" + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.3" }, "bin": [ "builds/pint" @@ -2769,7 +2739,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-01-05T16:49:17+00:00" + "time": "2026-04-20T15:26:14+00:00" }, { "name": "myclabs/deep-copy", @@ -3060,16 +3030,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.4.3", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "b641dde59d969ea42eed70a39f9b51950bc96878" + "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878", - "reference": "b641dde59d969ea42eed70a39f9b51950bc96878", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/661c8c6abbc7734986cf7bc6062c237fbb450461", + "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461", "shasum": "" }, "require": { @@ -3080,7 +3050,7 @@ "ext-reflection": "*", "ext-spl": "*", "ext-tokenizer": "*", - "php": "^8.1", + "php": "^8.2", "phpbench/container": "^2.2", "psr/log": "^1.1 || ^2.0 || ^3.0", "seld/jsonlint": "^1.1", @@ -3100,8 +3070,9 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.4 || ^11.0", + "phpunit/phpunit": "^11.5", "rector/rector": "^1.2", + "sebastian/exporter": "^6.3.2", "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" }, @@ -3146,7 +3117,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.4.3" + "source": "https://github.com/phpbench/phpbench/tree/1.6.1" }, "funding": [ { @@ -3154,15 +3125,15 @@ "type": "github" } ], - "time": "2025-11-06T19:07:31+00:00" + "time": "2026-03-22T10:27:20+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.32", + "version": "1.12.33", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", - "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", + "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", "shasum": "" }, "require": { @@ -3207,7 +3178,7 @@ "type": "github" } ], - "time": "2025-09-30T10:16:31+00:00" + "time": "2026-02-28T20:30:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3530,16 +3501,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.31", + "version": "9.6.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "945d0b7f346a084ce5549e95289962972c4272e5" + "reference": "b36f02317466907a230d3aa1d34467041271ef4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/945d0b7f346a084ce5549e95289962972c4272e5", - "reference": "945d0b7f346a084ce5549e95289962972c4272e5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", + "reference": "b36f02317466907a230d3aa1d34467041271ef4a", "shasum": "" }, "require": { @@ -3561,7 +3532,7 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.9", + "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.8", @@ -3613,7 +3584,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.31" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" }, "funding": [ { @@ -3637,7 +3608,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T07:45:52+00:00" + "time": "2026-01-27T05:45:00+00:00" }, { "name": "psr/cache", @@ -3857,16 +3828,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.9", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { @@ -3919,7 +3890,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" }, "funding": [ { @@ -3939,7 +3910,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T06:51:50+00:00" + "time": "2026-01-24T09:22:56+00:00" }, { "name": "sebastian/complexity", @@ -4765,16 +4736,16 @@ }, { "name": "symfony/console", - "version": "v8.0.3", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" + "reference": "7113778e2e91f4709cb3194a75dfa9c0d028d94d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", - "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "url": "https://api.github.com/repos/symfony/console/zipball/7113778e2e91f4709cb3194a75dfa9c0d028d94d", + "reference": "7113778e2e91f4709cb3194a75dfa9c0d028d94d", "shasum": "" }, "require": { @@ -4831,7 +4802,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.3" + "source": "https://github.com/symfony/console/tree/v8.0.9" }, "funding": [ { @@ -4851,20 +4822,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:52:06+00:00" + "time": "2026-04-29T15:02:55+00:00" }, { "name": "symfony/filesystem", - "version": "v8.0.1", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d" + "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", + "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", "shasum": "" }, "require": { @@ -4901,7 +4872,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + "source": "https://github.com/symfony/filesystem/tree/v8.0.9" }, "funding": [ { @@ -4921,20 +4892,20 @@ "type": "tidelift" } ], - "time": "2025-12-01T09:13:36+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/finder", - "version": "v8.0.3", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12" + "reference": "8da41214757b87d97f181e3d14a4179286151007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12", - "reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12", + "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", + "reference": "8da41214757b87d97f181e3d14a4179286151007", "shasum": "" }, "require": { @@ -4969,7 +4940,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.3" + "source": "https://github.com/symfony/finder/tree/v8.0.8" }, "funding": [ { @@ -4989,20 +4960,20 @@ "type": "tidelift" } ], - "time": "2025-12-23T14:52:06+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/options-resolver", - "version": "v8.0.0", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", - "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8", + "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8", "shasum": "" }, "require": { @@ -5040,7 +5011,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.8" }, "funding": [ { @@ -5060,20 +5031,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:55:31+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -5123,7 +5094,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -5143,20 +5114,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -5205,7 +5176,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -5225,11 +5196,11 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5290,7 +5261,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -5314,16 +5285,16 @@ }, { "name": "symfony/process", - "version": "v8.0.3", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "0cbbd88ec836f8757641c651bb995335846abb78" + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78", - "reference": "0cbbd88ec836f8757641c651bb995335846abb78", + "url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", "shasum": "" }, "require": { @@ -5355,7 +5326,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.3" + "source": "https://github.com/symfony/process/tree/v8.0.8" }, "funding": [ { @@ -5375,20 +5346,20 @@ "type": "tidelift" } ], - "time": "2025-12-19T10:01:18+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/string", - "version": "v8.0.1", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", - "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963", + "reference": "ae9488f874d7603f9d2dfbf120203882b645d963", "shasum": "" }, "require": { @@ -5445,7 +5416,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.1" + "source": "https://github.com/symfony/string/tree/v8.0.8" }, "funding": [ { @@ -5465,7 +5436,7 @@ "type": "tidelift" } ], - "time": "2025-12-01T09:13:36+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "theseer/tokenizer", @@ -5569,7 +5540,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -5578,6 +5549,6 @@ "ext-curl": "*", "ext-redis": "*" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index be0fa12e6cc..3b4d5444555 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -4,8 +4,9 @@ use Appwrite\AppwriteException; use Appwrite\Client; -use Appwrite\Enums\IndexType; +use Appwrite\Enums\TablesDBIndexType; use Appwrite\ID; +use Appwrite\Models\Row; use Appwrite\Query; use Appwrite\Services\TablesDB as TablesDBService; use Utopia\Abuse\Adapters\TimeLimit; @@ -94,8 +95,8 @@ protected function createColumns(): void protected function createIndexes(): void { $indexes = [ - fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', IndexType::UNIQUE(), ['key', 'time']), - fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', IndexType::KEY(), ['time']) + fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', TablesDBIndexType::UNIQUE(), ['key', 'time']), + fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', TablesDBIndexType::KEY(), ['time']) ]; foreach ($indexes as $createIndexFunction) { @@ -111,12 +112,11 @@ protected function waitForResourcesReady(string $resourceType): void while ($attempts < $maxAttempts) { $attempts++; - $response = $resourceType === 'columns' - ? $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)]) - : $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)]); + $resources = $resourceType === 'columns' + ? $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->columns + : $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->indexes; - $resources = $response[$resourceType]; - $resources = \array_filter($resources, fn ($resource) => $resource['status'] !== 'available'); + $resources = \array_filter($resources, fn ($resource) => $resource->status !== 'available'); if (\count($resources) === 0) { return; @@ -170,16 +170,15 @@ protected function count(string $key, int $timestamp): int $timestamp = $this->toDateTime($timestamp); - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; + ])->rows; $this->count = 0; if (\count($rows) === 1) { // Unique Index - $count = $rows[0]['count'] ?? 0; + $count = $rows[0]->data['count'] ?? 0; if (\is_numeric($count)) { $this->count = intval($count); } @@ -203,14 +202,13 @@ protected function hit(string $key, int $timestamp): void $timestamp = $this->toDateTime($timestamp); - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; - $data = $rows[0] ?? null; + ])->rows; + $row = $rows[0] ?? null; - if (\is_null($data)) { + if (\is_null($row)) { $data = [ 'key' => $key, 'time' => $timestamp, @@ -224,27 +222,26 @@ protected function hit(string $key, int $timestamp): void throw $err; } - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; + ])->rows; - $data = $rows[0] ?? null; + $row = $rows[0] ?? null; - if (!is_null($data)) { - $count = $data['count'] ?? 0; + if (!is_null($row)) { + $count = $row->data['count'] ?? 0; if (\is_numeric($count)) { $this->count = intval($count); } - $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $data['$id'], 'count', 1); + $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $row->id, 'count', 1); } else { throw new \Exception('Document Not Found'); } } } else { - $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $data['$id'], 'count', 1); + $this->tablesDB->incrementRowColumn($this->databaseId, self::TABLE_ID, $row->id, 'count', 1); } $this->count++; @@ -262,14 +259,13 @@ protected function set(string $key, int $timestamp, int $value): void { $timestamp = $this->toDateTime($timestamp); - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; - $data = $rows[0] ?? null; + ])->rows; + $row = $rows[0] ?? null; - if (\is_null($data)) { + if (\is_null($row)) { $data = [ 'key' => $key, 'time' => $timestamp, @@ -283,22 +279,21 @@ protected function set(string $key, int $timestamp, int $value): void throw $err; } - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, [ Query::equal('key', [$key]), Query::equal('time', [$timestamp]), - ]); - $rows = $response['rows']; + ])->rows; - $data = $rows[0] ?? null; + $row = $rows[0] ?? null; - if (!is_null($data)) { - $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + if (!is_null($row)) { + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $row->id, ['count' => $value]); } else { throw new \Exception('Unable to find abuse tracking row after race condition handling'); } } } else { - $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $data['$id'], ['count' => $value]); + $this->tablesDB->updateRow($this->databaseId, self::TABLE_ID, $row->id, ['count' => $value]); } $this->count = $value; @@ -328,9 +323,9 @@ public function getLogs(?int $offset = null, ?int $limit = 25): array $queries[] = Query::limit($limit); } - $response = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, $queries); + $rows = $this->tablesDB->listRows($this->databaseId, self::TABLE_ID, $queries)->rows; - return \array_map(fn ($document) => new Document($document), $response['documents']); + return \array_map(fn (Row $row) => new Document($row->toArray()), $rows); } /** @@ -349,7 +344,7 @@ public function cleanup(int $timestamp): bool $response = $this->tablesDB->deleteRows($this->databaseId, self::TABLE_ID, [ Query::lessThan('time', $timestamp), ]); - } while ($response['total'] > 0); + } while ($response->total > 0); return true; } From cfd290a843971d3aec8f4ef58d47761a7da5f436 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 11 May 2026 02:57:16 +0100 Subject: [PATCH 338/470] fix: require PHP >=8.2, fix latent ColumnStatus filter PHP version constraint (must-fix): - composer.json: php >=8.0 -> >=8.2 (SDK 22+ requires 8.2) - tests.yml matrix: drop 8.1 leg (composer install would fail) - Drop Dockerfile.php-8.1 (now dead) Column status filter (latent bug): - SDK 22+ exposes Column*::status as a ColumnStatus enum object; ColumnIndex::status is still string. The plain \$resource->status !== 'available' check is always true for column results (object !== string under strict equality), making the client-side safety filter dead code in the columns branch. Cast to string so both paths work. --- .github/workflows/tests.yml | 2 +- Dockerfile.php-8.1 | 27 ------------------ composer.json | 2 +- composer.lock | 28 +++++++++---------- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 3 +- 5 files changed, 18 insertions(+), 44 deletions(-) delete mode 100644 Dockerfile.php-8.1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c17c12fbc02..8c48f44f5b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.1', '8.2', '8.3'] + php-versions: ['8.2', '8.3'] steps: - name: Checkout repository diff --git a/Dockerfile.php-8.1 b/Dockerfile.php-8.1 deleted file mode 100644 index 321055524d6..00000000000 --- a/Dockerfile.php-8.1 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY composer.lock /src/ -COPY composer.json /src/ - -RUN composer install --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.1-0.1.0 as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./phpbench.json /code/phpbench.json - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/composer.json b/composer.json index 77e5d7ae126..2487bb1238b 100755 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "bench": "vendor/bin/phpbench run --report=aggregate" }, "require": { - "php": ">=8.0", + "php": ">=8.2", "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", diff --git a/composer.lock b/composer.lock index c960ddd9f2f..035181eb803 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2818364d7546938e790828df5ee307d1", + "content-hash": "b04b14bd2ea9045a6b56b7aa930aea9f", "packages": [ { "name": "appwrite/appwrite", - "version": "23.0.0", + "version": "23.1.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "d22b36167931294bec4259f0803aba14537030ec" + "reference": "2f275921f10ceb7cff99f2d463f7328b296234fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/d22b36167931294bec4259f0803aba14537030ec", - "reference": "d22b36167931294bec4259f0803aba14537030ec", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/2f275921f10ceb7cff99f2d463f7328b296234fa", + "reference": "2f275921f10ceb7cff99f2d463f7328b296234fa", "shasum": "" }, "require": { @@ -43,10 +43,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/23.0.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/23.1.0", "url": "https://appwrite.io/support" }, - "time": "2026-04-16T12:38:44+00:00" + "time": "2026-05-08T13:44:58+00:00" }, { "name": "brick/math", @@ -2076,16 +2076,16 @@ }, { "name": "utopia-php/cache", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c" + "reference": "d36f9050c39c02e09a7763389c9e71258e74af1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/05ceba981436a4022553f7aaa2a05fa049d0f71c", - "reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/d36f9050c39c02e09a7763389c9e71258e74af1f", + "reference": "d36f9050c39c02e09a7763389c9e71258e74af1f", "shasum": "" }, "require": { @@ -2122,9 +2122,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/1.0.1" + "source": "https://github.com/utopia-php/cache/tree/1.0.2" }, - "time": "2026-03-12T03:39:09+00:00" + "time": "2026-05-08T11:40:20+00:00" }, { "name": "utopia-php/console", @@ -5544,7 +5544,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0", + "php": ">=8.2", "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*" diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 3b4d5444555..bd34dcc2c2d 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -116,7 +116,8 @@ protected function waitForResourcesReady(string $resourceType): void ? $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->columns : $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->indexes; - $resources = \array_filter($resources, fn ($resource) => $resource->status !== 'available'); + // Column models expose status as a ColumnStatus enum; index models as a plain string. Cast for both. + $resources = \array_filter($resources, fn ($resource) => (string) $resource->status !== 'available'); if (\count($resources) === 0) { return; From a7a9bc33dd92fee3db8ddf6be5e29def8a5f13ad Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 11 May 2026 06:16:33 +0100 Subject: [PATCH 339/470] fix: drop stale Utopia\Exception import (phpstan level max) Utopia\Exception came transitively from utopia-php/framework, which the SDK 23 bump dropped from the dependency graph. Replace the unresolved import with the built-in \Exception and prune the dead @throws tags. --- src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php | 3 +-- src/Abuse/Adapters/TimeLimit/Database.php | 5 ++--- tests/Abuse/Bench/Database.php | 2 -- tests/Abuse/RedisClusterTest.php | 2 -- tests/Abuse/RedisTest.php | 2 -- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index bd34dcc2c2d..7c95da4f45e 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -11,7 +11,6 @@ use Appwrite\Services\TablesDB as TablesDBService; use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Database\Document; -use Utopia\Exception; class TablesDB extends TimeLimit { @@ -36,7 +35,7 @@ public function __construct(string $key, int $limit, int $seconds, Client $clien } /** - * @throws Exception|\Exception + * @throws \Exception */ public function setup(): void { diff --git a/src/Abuse/Adapters/TimeLimit/Database.php b/src/Abuse/Adapters/TimeLimit/Database.php index 01202fd5749..cf9c30cf618 100644 --- a/src/Abuse/Adapters/TimeLimit/Database.php +++ b/src/Abuse/Adapters/TimeLimit/Database.php @@ -10,7 +10,6 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Exception\Structure; use Utopia\Database\Query; -use Utopia\Exception; class Database extends TimeLimit { @@ -87,12 +86,12 @@ public function __construct(string $key, int $limit, int $seconds, UtopiaDB $db) /** * @throws Duplicate - * @throws Exception|\Exception + * @throws \Exception */ public function setup(): void { if (! $this->db->exists($this->db->getDatabase())) { - throw new Exception('You need to create database before running timelimit setup'); + throw new \Exception('You need to create database before running timelimit setup'); } $attributes = \array_map(function ($attribute) { diff --git a/tests/Abuse/Bench/Database.php b/tests/Abuse/Bench/Database.php index cbf9c6cef62..53983f1a835 100644 --- a/tests/Abuse/Bench/Database.php +++ b/tests/Abuse/Bench/Database.php @@ -10,14 +10,12 @@ use Utopia\Database\Adapter\MySQL; use Utopia\Database\Adapter\SQL; use Utopia\Database\Database as UtopiaDatabase; -use Utopia\Exception; final class Database extends Base { protected UtopiaDatabase $db; /** - * @throws Exception * @throws \Exception */ public function setUp(): void diff --git a/tests/Abuse/RedisClusterTest.php b/tests/Abuse/RedisClusterTest.php index e1029f946af..5e39581419e 100644 --- a/tests/Abuse/RedisClusterTest.php +++ b/tests/Abuse/RedisClusterTest.php @@ -4,14 +4,12 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Abuse\Adapters\TimeLimit\RedisCluster as AdapterRedisCluster; -use Utopia\Exception; class RedisClusterTest extends Base { protected static \RedisCluster $redis; /** - * @throws Exception * @throws \Exception */ public static function setUpBeforeClass(): void diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php index 085da1e4faf..23517edf861 100644 --- a/tests/Abuse/RedisTest.php +++ b/tests/Abuse/RedisTest.php @@ -5,14 +5,12 @@ use Redis; use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Abuse\Adapters\TimeLimit\Redis as AdapterRedis; -use Utopia\Exception; class RedisTest extends Base { protected static \Redis $redis; /** - * @throws Exception * @throws \Exception */ public static function setUpBeforeClass(): void From c20ded6aba4c0f5a7b818ac332c1605069752a9f Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 11 May 2026 08:14:37 +0100 Subject: [PATCH 340/470] ci: add PHP 8.4 to test matrix Adds Dockerfile.php-8.4 and bumps the existing 8.2 / 8.3 images to appwrite/utopia-base:php-X.Y-1.0.0 so all three stay aligned. --- .github/workflows/tests.yml | 2 +- Dockerfile.php-8.2 | 2 +- Dockerfile.php-8.3 | 2 +- Dockerfile.php-8.4 | 27 +++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 Dockerfile.php-8.4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c48f44f5b1..3ab375cc57a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.2', '8.3'] + php-versions: ['8.2', '8.3', '8.4'] steps: - name: Checkout repository diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index 8c2d4babc98..24e4214eda9 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM appwrite/utopia-base:php-8.2-0.1.0 as final +FROM appwrite/utopia-base:php-8.2-1.0.0 as final LABEL maintainer="team@appwrite.io" diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index 63f979822ce..175276f3925 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -8,7 +8,7 @@ COPY composer.json /src/ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist -FROM appwrite/utopia-base:php-8.3-0.1.0 as final +FROM appwrite/utopia-base:php-8.3-1.0.0 as final LABEL maintainer="team@appwrite.io" diff --git a/Dockerfile.php-8.4 b/Dockerfile.php-8.4 new file mode 100644 index 00000000000..8e929fbbe20 --- /dev/null +++ b/Dockerfile.php-8.4 @@ -0,0 +1,27 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY composer.lock /src/ +COPY composer.json /src/ + +RUN composer install --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM appwrite/utopia-base:php-8.4-1.0.0 as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml +COPY ./phpbench.json /code/phpbench.json + +CMD [ "tail", "-f", "/dev/null" ] From 5f1b5e2594636ac6c358b16c6dedd1ec6807f764 Mon Sep 17 00:00:00 2001 From: premtsd-code Date: Wed, 20 May 2026 11:54:49 +0100 Subject: [PATCH 341/470] feat: bump appwrite/appwrite to 24.* The SDK's 24.0.0 release renamed several Project-prefixed enums (AuthMethod -> ProjectAuthMethodId, ServiceId -> ProjectServiceId, ProtocolId -> ProjectProtocolId, ProjectPolicy -> ProjectPolicyId) and 24.1.0 made BillingLimits fields nullable to match the server's sparse 'limits crossed' response. abuse doesn't use any of the renamed enums; it only imports Client, AppwriteException, ID, Query, Models\Row, Services\TablesDB, and Enums\TablesDBIndexType -- all unchanged in 24.x. Composer lock updated to 24.1.0. Bump unblocks downstream consumers like utopia-php/migration that need the 24.x SDK. --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 2487bb1238b..6dae0b9bc3e 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "5.*", - "appwrite/appwrite": "23.*" + "appwrite/appwrite": "24.*" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 035181eb803..23ff3abef0b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b04b14bd2ea9045a6b56b7aa930aea9f", + "content-hash": "c5a86c0dbd34a49a1f91118323c731c1", "packages": [ { "name": "appwrite/appwrite", - "version": "23.1.0", + "version": "24.1.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "2f275921f10ceb7cff99f2d463f7328b296234fa" + "reference": "dcb3550a3332de1c1665a015a09e9c73ff515e4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/2f275921f10ceb7cff99f2d463f7328b296234fa", - "reference": "2f275921f10ceb7cff99f2d463f7328b296234fa", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/dcb3550a3332de1c1665a015a09e9c73ff515e4f", + "reference": "dcb3550a3332de1c1665a015a09e9c73ff515e4f", "shasum": "" }, "require": { @@ -43,10 +43,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/23.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/24.1.0", "url": "https://appwrite.io/support" }, - "time": "2026-05-08T13:44:58+00:00" + "time": "2026-05-20T09:37:03+00:00" }, { "name": "brick/math", From 20400c5dd81ca63432ba15657ac451829cac22ec Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 27 May 2026 12:56:33 +0200 Subject: [PATCH 342/470] feat: initial setup of the nats.php client --- .gitignore | 13 + README.md | 339 +++++ composer.json | 30 + composer.lock | 1693 +++++++++++++++++++++ examples/jetstream.php | 54 + examples/publish.php | 26 + examples/request_reply.php | 27 + examples/subscribe.php | 28 + phpunit.xml | 23 + src/Auth/Authenticator.php | 16 + src/Auth/CredentialsAuth.php | 67 + src/Auth/NKeyAuth.php | 102 ++ src/Auth/NoAuth.php | 13 + src/Auth/TokenAuth.php | 20 + src/Auth/UserPassAuth.php | 22 + src/Connection.php | 771 ++++++++++ src/ConnectionOptions.php | 56 + src/Exception/AuthenticationException.php | 9 + src/Exception/ConnectionException.php | 9 + src/Exception/JetStreamException.php | 19 + src/Exception/KeyValueException.php | 9 + src/Exception/MaxPayloadException.php | 9 + src/Exception/NatsException.php | 9 + src/Exception/PermissionException.php | 9 + src/Exception/ProtocolException.php | 9 + src/Exception/TimeoutException.php | 9 + src/Headers.php | 149 ++ src/Inbox.php | 29 + src/JetStream/AckPolicy.php | 12 + src/JetStream/ApiError.php | 24 + src/JetStream/Consumer.php | 104 ++ src/JetStream/ConsumerConfig.php | 121 ++ src/JetStream/ConsumerInfo.php | 34 + src/JetStream/DeliverPolicy.php | 15 + src/JetStream/DiscardPolicy.php | 11 + src/JetStream/JetStream.php | 232 +++ src/JetStream/JetStreamMessage.php | 85 ++ src/JetStream/MessageBatch.php | 40 + src/JetStream/MsgMetadata.php | 60 + src/JetStream/PubAck.php | 26 + src/JetStream/ReplayPolicy.php | 11 + src/JetStream/RetentionPolicy.php | 12 + src/JetStream/StorageType.php | 11 + src/JetStream/Stream.php | 62 + src/JetStream/StreamConfig.php | 115 ++ src/JetStream/StreamInfo.php | 24 + src/JetStream/StreamState.php | 32 + src/KeyValue/KeyValue.php | 197 +++ src/KeyValue/KeyValueConfig.php | 48 + src/KeyValue/KeyValueEntry.php | 18 + src/KeyValue/KeyValueOperation.php | 12 + src/KeyValue/KeyValueStatus.php | 20 + src/Message.php | 17 + src/Protocol/Command.php | 16 + src/Protocol/Parser.php | 188 +++ src/Protocol/ServerOp.php | 16 + src/Protocol/Writer.php | 64 + src/ServerInfo.php | 50 + src/Subscription.php | 118 ++ src/Transport/TcpTransport.php | 175 +++ src/Transport/TlsTransport.php | 161 ++ src/Transport/Transport.php | 22 + tests/Integration/ConnectionTest.php | 57 + tests/Integration/PubSubTest.php | 130 ++ tests/Integration/RequestReplyTest.php | 48 + tests/Unit/HeadersTest.php | 124 ++ tests/Unit/InboxTest.php | 38 + tests/Unit/Protocol/ParserTest.php | 174 +++ tests/Unit/Protocol/WriterTest.php | 94 ++ 69 files changed, 6387 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 examples/jetstream.php create mode 100644 examples/publish.php create mode 100644 examples/request_reply.php create mode 100644 examples/subscribe.php create mode 100644 phpunit.xml create mode 100644 src/Auth/Authenticator.php create mode 100644 src/Auth/CredentialsAuth.php create mode 100644 src/Auth/NKeyAuth.php create mode 100644 src/Auth/NoAuth.php create mode 100644 src/Auth/TokenAuth.php create mode 100644 src/Auth/UserPassAuth.php create mode 100644 src/Connection.php create mode 100644 src/ConnectionOptions.php create mode 100644 src/Exception/AuthenticationException.php create mode 100644 src/Exception/ConnectionException.php create mode 100644 src/Exception/JetStreamException.php create mode 100644 src/Exception/KeyValueException.php create mode 100644 src/Exception/MaxPayloadException.php create mode 100644 src/Exception/NatsException.php create mode 100644 src/Exception/PermissionException.php create mode 100644 src/Exception/ProtocolException.php create mode 100644 src/Exception/TimeoutException.php create mode 100644 src/Headers.php create mode 100644 src/Inbox.php create mode 100644 src/JetStream/AckPolicy.php create mode 100644 src/JetStream/ApiError.php create mode 100644 src/JetStream/Consumer.php create mode 100644 src/JetStream/ConsumerConfig.php create mode 100644 src/JetStream/ConsumerInfo.php create mode 100644 src/JetStream/DeliverPolicy.php create mode 100644 src/JetStream/DiscardPolicy.php create mode 100644 src/JetStream/JetStream.php create mode 100644 src/JetStream/JetStreamMessage.php create mode 100644 src/JetStream/MessageBatch.php create mode 100644 src/JetStream/MsgMetadata.php create mode 100644 src/JetStream/PubAck.php create mode 100644 src/JetStream/ReplayPolicy.php create mode 100644 src/JetStream/RetentionPolicy.php create mode 100644 src/JetStream/StorageType.php create mode 100644 src/JetStream/Stream.php create mode 100644 src/JetStream/StreamConfig.php create mode 100644 src/JetStream/StreamInfo.php create mode 100644 src/JetStream/StreamState.php create mode 100644 src/KeyValue/KeyValue.php create mode 100644 src/KeyValue/KeyValueConfig.php create mode 100644 src/KeyValue/KeyValueEntry.php create mode 100644 src/KeyValue/KeyValueOperation.php create mode 100644 src/KeyValue/KeyValueStatus.php create mode 100644 src/Message.php create mode 100644 src/Protocol/Command.php create mode 100644 src/Protocol/Parser.php create mode 100644 src/Protocol/ServerOp.php create mode 100644 src/Protocol/Writer.php create mode 100644 src/ServerInfo.php create mode 100644 src/Subscription.php create mode 100644 src/Transport/TcpTransport.php create mode 100644 src/Transport/TlsTransport.php create mode 100644 src/Transport/Transport.php create mode 100644 tests/Integration/ConnectionTest.php create mode 100644 tests/Integration/PubSubTest.php create mode 100644 tests/Integration/RequestReplyTest.php create mode 100644 tests/Unit/HeadersTest.php create mode 100644 tests/Unit/InboxTest.php create mode 100644 tests/Unit/Protocol/ParserTest.php create mode 100644 tests/Unit/Protocol/WriterTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..958e783be05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/vendor/ +composer.phar +.phpunit.result.cache +.phpunit.cache/ +*.cache +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db +.env diff --git a/README.md b/README.md new file mode 100644 index 00000000000..2028f75287e --- /dev/null +++ b/README.md @@ -0,0 +1,339 @@ +# nats.php + +A modern PHP client for [NATS](https://nats.io) messaging system with JetStream and Key-Value store support. + +## Requirements + +- PHP 8.1+ +- `ext-json` +- `ext-sodium` (optional, for NKey/JWT authentication) + +## Installation + +```bash +composer require nats/nats +``` + +## Quick Start + +```php +use Nats\Connection; + +$conn = Connection::connect('nats://127.0.0.1:4222'); + +// Publish +$conn->publish('greet.world', 'Hello, World!'); + +// Subscribe +$conn->subscribe('greet.*', function ($msg) { + echo "Received: {$msg->data}\n"; +}); + +// Process messages +$conn->wait(); +``` + +## Core NATS + +### Connecting + +```php +use Nats\Connection; +use Nats\ConnectionOptions; + +// Simple +$conn = Connection::connect('nats://127.0.0.1:4222'); + +// With options +$conn = Connection::connect(new ConnectionOptions( + servers: ['nats://host1:4222', 'nats://host2:4222'], + name: 'my-service', + user: 'alice', + pass: 'secret', + connectTimeout: 5.0, + allowReconnect: true, + maxReconnectAttempts: 60, +)); + +// From URL with credentials +$conn = Connection::connect('nats://user:pass@127.0.0.1:4222'); +``` + +### Publishing + +```php +use Nats\Headers; + +// Simple publish +$conn->publish('orders.new', '{"id": 1}'); + +// Publish with headers +$headers = new Headers(); +$headers->set('Content-Type', 'application/json'); +$headers->set('X-Trace-Id', 'abc-123'); +$conn->publish('orders.new', '{"id": 1}', headers: $headers); +``` + +### Subscribing + +```php +// Async with callback +$sub = $conn->subscribe('orders.*', function ($msg) { + echo "{$msg->subject}: {$msg->data}\n"; +}); + +// Sync +$sub = $conn->subscribe('orders.new'); +$msg = $sub->nextMessage(timeout: 5.0); + +// Wildcards +$conn->subscribe('events.>', fn($msg) => handle($msg)); // multi-level +$conn->subscribe('orders.*', fn($msg) => handle($msg)); // single-level + +// Queue groups (load-balanced) +$conn->queueSubscribe('tasks', 'workers', function ($msg) { + processTask($msg->data); +}); + +// Unsubscribe +$sub->unsubscribe(); + +// Auto-unsubscribe after N messages +$conn->unsubscribe($sub, maxMessages: 10); +``` + +### Request-Reply + +```php +// Responder +$conn->subscribe('math.double', function ($msg) use ($conn) { + $value = (int) $msg->data; + $conn->publish($msg->replyTo, (string) ($value * 2)); +}); + +// Requester +$response = $conn->request('math.double', '21', timeout: 2.0); +echo $response->data; // "42" +``` + +### Connection Management + +```php +$conn->flush(); // Ensure all messages are sent +$conn->drain(); // Gracefully close (flush + unsubscribe) +$conn->close(); // Immediate close + +$conn->isConnected(); // Check status +$conn->getServerInfo()->version; // Server info +``` + +## JetStream + +### Streams + +```php +use Nats\JetStream\StreamConfig; +use Nats\JetStream\StorageType; +use Nats\JetStream\RetentionPolicy; + +$js = $conn->jetStream(); + +// Create a stream +$stream = $js->createOrUpdateStream(new StreamConfig( + name: 'ORDERS', + subjects: ['orders.>'], + storage: StorageType::File, + retention: RetentionPolicy::Limits, + maxAge: 86400.0, // 1 day in seconds + replicas: 1, +)); + +// Stream info +$info = $stream->info(refresh: true); +echo "Messages: {$info->state->messages}\n"; + +// List streams +$names = $js->getStreamNames(); + +// Delete +$stream->delete(); +``` + +### Publishing with Acknowledgment + +```php +$ack = $js->publish('orders.new', '{"id": 1}'); +echo "Stream: {$ack->stream}, Seq: {$ack->sequence}\n"; + +// With deduplication +$ack = $js->publish('orders.new', '{"id": 1}', msgId: 'order-1'); + +// With expected sequence (optimistic concurrency) +$ack = $js->publish('orders.new', $data, expectedLastSeq: 42); +``` + +### Consumers + +```php +use Nats\JetStream\ConsumerConfig; +use Nats\JetStream\AckPolicy; +use Nats\JetStream\DeliverPolicy; + +// Create a pull consumer +$consumer = $js->createConsumer('ORDERS', new ConsumerConfig( + name: 'order-processor', + durableName: 'order-processor', + ackPolicy: AckPolicy::Explicit, + filterSubject: 'orders.>', + deliverPolicy: DeliverPolicy::All, +)); + +// Fetch a batch of messages +$batch = $consumer->fetch(batch: 10, timeout: 5.0); +foreach ($batch as $msg) { + echo "Processing: {$msg->getData()}\n"; + + $msg->ack(); // Acknowledge + // $msg->nak(); // Negative ack (redeliver) + // $msg->term(); // Terminate (no redeliver) + // $msg->inProgress(); // Extend ack deadline +} + +// Fetch single message +$msg = $consumer->next(timeout: 5.0); + +// Message metadata +$meta = $msg->metadata(); +echo "Stream seq: {$meta->streamSequence}\n"; +echo "Deliveries: {$meta->numDelivered}\n"; +``` + +## Key-Value Store + +```php +use Nats\KeyValue\KeyValueConfig; +use Nats\JetStream\StorageType; + +$js = $conn->jetStream(); + +// Create a KV bucket +$kv = $js->createKeyValue(new KeyValueConfig( + bucket: 'config', + history: 5, + ttl: 3600.0, // 1 hour + storage: StorageType::File, +)); + +// Put +$revision = $kv->put('app.name', 'My Service'); + +// Get +$entry = $kv->get('app.name'); +echo "{$entry->key} = {$entry->value} (rev: {$entry->revision})\n"; + +// Create (fails if key exists) +$revision = $kv->create('app.version', '1.0.0'); + +// Update with CAS (compare-and-swap) +$revision = $kv->update('app.version', '1.1.0', revision: $revision); + +// Delete / Purge +$kv->delete('app.name'); +$kv->purge('app.version'); + +// List keys +$keys = $kv->keys(); + +// Bucket status +$status = $kv->status(); +echo "Values: {$status->values}, Bytes: {$status->bytes}\n"; +``` + +## Authentication + +```php +use Nats\ConnectionOptions; + +// User/Password +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + user: 'alice', + pass: 'secret', +)); + +// Token +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + token: 'my-token', +)); + +// NKey (requires ext-sodium) +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + nkey: 'UABC...', + nkeySeed: 'SUABC...', +)); + +// JWT Credentials file (requires ext-sodium) +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + credentialsFile: '/path/to/user.creds', +)); +``` + +## TLS + +```php +use Nats\ConnectionOptions; + +// TLS with system CA +$conn = Connection::connect(new ConnectionOptions( + servers: 'tls://nats.example.com:4222', +)); + +// TLS with custom CA and client certificates (mTLS) +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + tls: true, + tlsCaFile: '/path/to/ca.pem', + tlsCertFile: '/path/to/client-cert.pem', + tlsKeyFile: '/path/to/client-key.pem', +)); +``` + +## Event Callbacks + +```php +$conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + onDisconnect: function () { + echo "Disconnected!\n"; + }, + onReconnect: function () { + echo "Reconnected!\n"; + }, + onClose: function () { + echo "Connection closed.\n"; + }, + onError: function ($e) { + echo "Error: {$e->getMessage()}\n"; + }, +)); +``` + +## Testing + +```bash +# Unit tests +./vendor/bin/phpunit --testsuite unit + +# Integration tests (requires a running nats-server) +./vendor/bin/phpunit --testsuite integration + +# With custom NATS URL +NATS_URL=nats://host:4222 ./vendor/bin/phpunit --testsuite integration +``` + +## License + +Apache-2.0 diff --git a/composer.json b/composer.json new file mode 100644 index 00000000000..bb493b1195b --- /dev/null +++ b/composer.json @@ -0,0 +1,30 @@ +{ + "name": "nats/nats", + "description": "Modern PHP client for NATS messaging system", + "type": "library", + "license": "Apache-2.0", + "keywords": ["nats", "messaging", "pubsub", "jetstream", "queue"], + "require": { + "php": ">=8.1", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-sodium": "Required for NKey and JWT credential authentication" + }, + "autoload": { + "psr-4": { + "Nats\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Nats\\Tests\\": "tests/" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000000..bf220be3be9 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1693 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "85bcd1dc40ee2a844faa03163ff3dda1", + "packages": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.63", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "33198268dad71e926626b618f3ec3966661e4d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-01-27T05:48:37+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", + "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-10T07:50:56+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1", + "ext-json": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/examples/jetstream.php b/examples/jetstream.php new file mode 100644 index 00000000000..a5a8c0fc04b --- /dev/null +++ b/examples/jetstream.php @@ -0,0 +1,54 @@ +jetStream(); + +echo "Connected to NATS with JetStream\n"; + +// Create a stream +$stream = $js->createOrUpdateStream(new StreamConfig( + name: 'ORDERS', + subjects: ['orders.>'], +)); +echo "Stream ORDERS created\n"; + +// Publish messages +for ($i = 1; $i <= 5; $i++) { + $ack = $js->publish('orders.new', json_encode(['id' => $i, 'item' => "Item {$i}"])); + echo "Published order {$i}, stream seq: {$ack->sequence}\n"; +} + +// Create a pull consumer +$consumer = $js->createConsumer('ORDERS', new ConsumerConfig( + name: 'order-processor', + durableName: 'order-processor', + ackPolicy: AckPolicy::Explicit, + filterSubject: 'orders.>', +)); +echo "Consumer created: {$consumer->getName()}\n"; + +// Fetch messages +$batch = $consumer->fetch(5, 5.0); +echo "Fetched {$batch->count()} messages\n"; + +foreach ($batch as $msg) { + $data = json_decode($msg->getData(), true); + echo "Processing order: {$data['id']} - {$data['item']}\n"; + $msg->ack(); +} + +// Clean up +$stream->delete(); +echo "Stream deleted\n"; + +$conn->close(); +echo "Done\n"; diff --git a/examples/publish.php b/examples/publish.php new file mode 100644 index 00000000000..e3a9bd55d0c --- /dev/null +++ b/examples/publish.php @@ -0,0 +1,26 @@ +publish('greet.world', 'Hello, World!'); +echo "Published to greet.world\n"; + +// Publish with headers +$headers = new \Nats\Headers(); +$headers->set('Content-Type', 'application/json'); +$conn->publish('events.user', '{"action":"login","user":"alice"}', headers: $headers); +echo "Published to events.user with headers\n"; + +$conn->flush(); +$conn->close(); + +echo "Done\n"; diff --git a/examples/request_reply.php b/examples/request_reply.php new file mode 100644 index 00000000000..1f1ef83e2a3 --- /dev/null +++ b/examples/request_reply.php @@ -0,0 +1,27 @@ +subscribe('echo', function (Message $msg) use ($conn) { + echo "Service received: {$msg->data}\n"; + if ($msg->replyTo !== null) { + $conn->publish($msg->replyTo, 'Echo: ' . $msg->data); + } +}); + +// Send a request +$response = $conn->request('echo', 'Hello, NATS!', 2.0); +echo "Got response: {$response->data}\n"; + +$conn->close(); +echo "Done\n"; diff --git a/examples/subscribe.php b/examples/subscribe.php new file mode 100644 index 00000000000..dd8974f4471 --- /dev/null +++ b/examples/subscribe.php @@ -0,0 +1,28 @@ +subscribe('greet.*', function (Message $msg) { + echo "Received on {$msg->subject}: {$msg->data}\n"; + + if ($msg->headers !== null) { + foreach ($msg->headers->all() as $name => $values) { + echo " Header {$name}: " . implode(', ', $values) . "\n"; + } + } +}); + +echo "Subscribed to greet.*, waiting for messages... (Ctrl+C to quit)\n"; + +// Process messages forever +$conn->wait(); diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000000..b55911d431b --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + tests/Unit + + + tests/Integration + + + + + src + + + diff --git a/src/Auth/Authenticator.php b/src/Auth/Authenticator.php new file mode 100644 index 00000000000..389817a3db5 --- /dev/null +++ b/src/Auth/Authenticator.php @@ -0,0 +1,16 @@ + + */ + public function authenticate(?string $nonce = null): array; +} diff --git a/src/Auth/CredentialsAuth.php b/src/Auth/CredentialsAuth.php new file mode 100644 index 00000000000..7596d388484 --- /dev/null +++ b/src/Auth/CredentialsAuth.php @@ -0,0 +1,67 @@ +jwt = $jwt; + // We need the public key from the seed for NKeyAuth, but for creds auth + // we derive it. For now, pass empty and override authenticate(). + $this->nkeyAuth = new NKeyAuth('', $seed); + } + + public function authenticate(?string $nonce = null): array + { + $nkeyFields = $this->nkeyAuth->authenticate($nonce); + + return [ + 'jwt' => $this->jwt, + 'nkey' => $nkeyFields['nkey'] ?? '', + 'sig' => $nkeyFields['sig'] ?? '', + ]; + } + + private static function extractBetween(string $content, string $begin, string $end): ?string + { + $startPos = strpos($content, $begin); + if ($startPos === false) { + return null; + } + + $startPos += strlen($begin); + $endPos = strpos($content, $end, $startPos); + if ($endPos === false) { + return null; + } + + return trim(substr($content, $startPos, $endPos - $startPos)); + } +} diff --git a/src/Auth/NKeyAuth.php b/src/Auth/NKeyAuth.php new file mode 100644 index 00000000000..8c1327345bc --- /dev/null +++ b/src/Auth/NKeyAuth.php @@ -0,0 +1,102 @@ +publicKey = $nkey; + $this->seed = $nkeySeed; + } + + public function authenticate(?string $nonce = null): array + { + if ($nonce === null) { + throw new AuthenticationException('NKey authentication requires a server nonce'); + } + + $rawSeed = self::decodeSeed($this->seed); + $keyPair = sodium_crypto_sign_seed_keypair($rawSeed); + $secretKey = sodium_crypto_sign_secretkey($keyPair); + $signature = sodium_crypto_sign_detached($nonce, $secretKey); + + return [ + 'nkey' => $this->publicKey, + 'sig' => self::base32Encode($signature), + ]; + } + + private static function decodeSeed(string $seed): string + { + $decoded = self::base32Decode($seed); + if (strlen($decoded) < 4) { + throw new AuthenticationException('Invalid NKey seed'); + } + + // Remove the 2-byte prefix and 2-byte CRC suffix + return substr($decoded, 2, -2); + } + + private static function base32Decode(string $input): string + { + $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + $input = strtoupper(rtrim($input, '=')); + $output = ''; + $buffer = 0; + $bitsLeft = 0; + + for ($i = 0, $len = strlen($input); $i < $len; $i++) { + $val = strpos($alphabet, $input[$i]); + if ($val === false) { + throw new AuthenticationException('Invalid base32 character in NKey seed'); + } + $buffer = ($buffer << 5) | $val; + $bitsLeft += 5; + + if ($bitsLeft >= 8) { + $bitsLeft -= 8; + $output .= chr(($buffer >> $bitsLeft) & 0xFF); + } + } + + return $output; + } + + private static function base32Encode(string $input): string + { + $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + $output = ''; + $buffer = 0; + $bitsLeft = 0; + + for ($i = 0, $len = strlen($input); $i < $len; $i++) { + $buffer = ($buffer << 8) | ord($input[$i]); + $bitsLeft += 8; + + while ($bitsLeft >= 5) { + $bitsLeft -= 5; + $output .= $alphabet[($buffer >> $bitsLeft) & 0x1F]; + } + } + + if ($bitsLeft > 0) { + $output .= $alphabet[($buffer << (5 - $bitsLeft)) & 0x1F]; + } + + return $output; + } +} diff --git a/src/Auth/NoAuth.php b/src/Auth/NoAuth.php new file mode 100644 index 00000000000..c5eebd5b439 --- /dev/null +++ b/src/Auth/NoAuth.php @@ -0,0 +1,13 @@ + $this->token, + ]; + } +} diff --git a/src/Auth/UserPassAuth.php b/src/Auth/UserPassAuth.php new file mode 100644 index 00000000000..cee7610d145 --- /dev/null +++ b/src/Auth/UserPassAuth.php @@ -0,0 +1,22 @@ + $this->user, + 'pass' => $this->pass, + ]; + } +} diff --git a/src/Connection.php b/src/Connection.php new file mode 100644 index 00000000000..749534773eb --- /dev/null +++ b/src/Connection.php @@ -0,0 +1,771 @@ + */ + private array $subscriptions = []; + private int $nextSid = 1; + private string $status = self::STATUS_DISCONNECTED; + private int $outstandingPings = 0; + private float $lastPingTime = 0.0; + + // Mux inbox for request-reply + private ?Subscription $inboxSub = null; + private string $inboxPrefix = ''; + /** @var array */ + private array $pendingRequests = []; + + // Reconnection + /** @var list */ + private array $serverPool = []; + private int $currentServerIndex = 0; + private int $reconnectAttempts = 0; + /** @var list */ + private array $pendingBuffer = []; + + private function __construct() + { + $this->writer = new Writer(); + } + + /** + * Connect to a NATS server. + * + * @param string|list|ConnectionOptions $urlOrOptions + */ + public static function connect( + string|array|ConnectionOptions $urlOrOptions = 'nats://127.0.0.1:4222', + ?ConnectionOptions $options = null, + ): self { + if ($urlOrOptions instanceof ConnectionOptions) { + $options = $urlOrOptions; + } elseif ($options === null) { + $options = new ConnectionOptions(servers: $urlOrOptions); + } + + $conn = new self(); + $conn->options = $options; + $conn->auth = $conn->resolveAuthenticator($options); + $conn->serverPool = $conn->buildServerPool($options); + $conn->doConnect(); + + return $conn; + } + + public function publish(string $subject, string $data = '', ?string $replyTo = null, ?Headers $headers = null): void + { + $this->ensureConnected(); + + if (isset($this->serverInfo) && strlen($data) > $this->serverInfo->maxPayload) { + throw new MaxPayloadException( + "Payload size " . strlen($data) . " exceeds server maximum of {$this->serverInfo->maxPayload}" + ); + } + + if ($headers !== null && !empty($headers->all())) { + $headerWire = $headers->toWire(); + $cmd = $this->writer->hpub($subject, $headerWire, $data, $replyTo); + } else { + $cmd = $this->writer->pub($subject, $data, $replyTo); + } + + $this->send($cmd); + } + + public function subscribe(string $subject, ?\Closure $callback = null, ?string $queue = null): Subscription + { + $this->ensureConnected(); + + $sid = (string) $this->nextSid++; + $sub = new Subscription($sid, $subject, $queue, $callback); + $sub->setConnection($this); + + $this->subscriptions[$sid] = $sub; + $this->send($this->writer->sub($subject, $sid, $queue)); + + return $sub; + } + + public function queueSubscribe(string $subject, string $queue, ?\Closure $callback = null): Subscription + { + return $this->subscribe($subject, $callback, $queue); + } + + public function unsubscribe(Subscription $sub, ?int $maxMessages = null): void + { + if ($maxMessages !== null) { + $sub->setMaxMessages($sub->getReceived() + $maxMessages); + $this->send($this->writer->unsub($sub->sid, $maxMessages)); + } else { + $sub->setInactive(); + unset($this->subscriptions[$sub->sid]); + $this->send($this->writer->unsub($sub->sid)); + } + } + + public function request(string $subject, string $data = '', ?float $timeout = null, ?Headers $headers = null): Message + { + $this->ensureConnected(); + + $timeout ??= $this->options->requestTimeout; + $this->ensureInboxSub(); + + $token = Inbox::generateId(); + $replyTo = $this->inboxPrefix . '.' . $token; + $this->pendingRequests[$token] = ['message' => null, 'resolved' => false]; + + $this->publish($subject, $data, $replyTo, $headers); + + $deadline = microtime(true) + $timeout; + + while (!$this->pendingRequests[$token]['resolved']) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + unset($this->pendingRequests[$token]); + throw new TimeoutException("Request timed out after {$timeout}s"); + } + + $this->processMessage($remaining); + } + + $msg = $this->pendingRequests[$token]['message']; + unset($this->pendingRequests[$token]); + + if ($msg === null) { + throw new TimeoutException("Request timed out after {$timeout}s"); + } + + // Check for no responders (status 503) + if ($msg->headers !== null && $msg->headers->getStatus() === '503') { + throw new NatsException('No responders for request'); + } + + return $msg; + } + + public function newInbox(): string + { + return Inbox::create($this->options->inboxPrefix); + } + + /** + * Read and dispatch one server message. + */ + public function processMessage(?float $timeout = null): ?Message + { + $this->checkPings(); + + try { + [$op, $data] = $this->parser->next($timeout); + } catch (TimeoutException) { + return null; + } catch (ConnectionException $e) { + if ($this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { + $this->attemptReconnect(); + return null; + } + throw $e; + } + + return $this->dispatchOp($op, $data); + } + + /** + * Process messages in a loop. + * + * @param int $count Number of messages to process (0 = forever) + */ + public function wait(int $count = 0, ?float $timeout = null): void + { + $processed = 0; + $deadline = $timeout !== null ? microtime(true) + $timeout : null; + + while ($count === 0 || $processed < $count) { + $remaining = $deadline !== null ? $deadline - microtime(true) : null; + if ($remaining !== null && $remaining <= 0) { + return; + } + + $msg = $this->processMessage($remaining); + if ($msg !== null) { + $processed++; + } + } + } + + public function jetStream(?string $domain = null, ?string $apiPrefix = null): JetStream\JetStream + { + return new JetStream\JetStream($this, $domain, $apiPrefix); + } + + public function flush(?float $timeout = null): void + { + $this->ensureConnected(); + $timeout ??= $this->options->connectTimeout; + + $this->send($this->writer->ping()); + $deadline = microtime(true) + $timeout; + + while (true) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + throw new TimeoutException('Flush timed out'); + } + + [$op, $data] = $this->parser->next($remaining); + if ($op === ServerOp::Pong) { + $this->outstandingPings = 0; + return; + } + $this->dispatchOp($op, $data); + } + } + + public function drain(?float $timeout = null): void + { + if ($this->status !== self::STATUS_CONNECTED) { + return; + } + + $this->status = self::STATUS_DRAINING; + $timeout ??= $this->options->drainTimeout; + + // Unsub all subscriptions + foreach ($this->subscriptions as $sub) { + $this->send($this->writer->unsub($sub->sid)); + } + + // Process remaining messages + $deadline = microtime(true) + $timeout; + while (!empty($this->subscriptions)) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + break; + } + + try { + $this->processMessage($remaining); + } catch (TimeoutException) { + break; + } + } + + $this->close(); + } + + public function close(): void + { + if ($this->status === self::STATUS_CLOSED) { + return; + } + + $previousStatus = $this->status; + $this->status = self::STATUS_CLOSED; + + foreach ($this->subscriptions as $sub) { + $sub->setInactive(); + } + $this->subscriptions = []; + $this->pendingRequests = []; + + if (isset($this->transport)) { + $this->transport->close(); + } + + if ($previousStatus === self::STATUS_CONNECTED && $this->options->onClose !== null) { + ($this->options->onClose)(); + } + } + + public function isConnected(): bool + { + return $this->status === self::STATUS_CONNECTED; + } + + public function isClosed(): bool + { + return $this->status === self::STATUS_CLOSED; + } + + public function isReconnecting(): bool + { + return $this->status === self::STATUS_RECONNECTING; + } + + public function getServerInfo(): ServerInfo + { + return $this->serverInfo; + } + + public function getStatus(): string + { + return $this->status; + } + + // --- Internal --- + + private function doConnect(): void + { + $this->status = self::STATUS_CONNECTING; + $lastError = null; + + foreach ($this->serverPool as $index => $url) { + try { + $this->connectToServer($url); + $this->currentServerIndex = $index; + $this->status = self::STATUS_CONNECTED; + $this->reconnectAttempts = 0; + return; + } catch (\Throwable $e) { + $lastError = $e; + continue; + } + } + + $this->status = self::STATUS_DISCONNECTED; + throw new ConnectionException( + 'Failed to connect to any NATS server', + previous: $lastError, + ); + } + + private function connectToServer(string $url): void + { + $parsed = $this->parseUrl($url); + $host = $parsed['host']; + $port = $parsed['port']; + $scheme = $parsed['scheme']; + + // Create transport + if ($scheme === 'tls' || $this->options->tls) { + $this->transport = new TlsTransport([ + 'cafile' => $this->options->tlsCaFile, + 'local_cert' => $this->options->tlsCertFile, + 'local_pk' => $this->options->tlsKeyFile, + ]); + } else { + $this->transport = new TcpTransport(); + } + + $this->transport->connect($host, $port, $this->options->connectTimeout); + $this->parser = new Parser($this->transport); + + // Read INFO + [$op, $data] = $this->parser->next($this->options->connectTimeout); + if ($op !== ServerOp::Info) { + throw new ProtocolException("Expected INFO, got {$op->value}"); + } + $this->serverInfo = ServerInfo::fromArray($data); + + // Merge connect_urls into server pool + foreach ($this->serverInfo->connectUrls as $connectUrl) { + if (!in_array($connectUrl, $this->serverPool, true)) { + $this->serverPool[] = $this->normalizeUrl($connectUrl); + } + } + + // TLS upgrade if required by server + if ($this->serverInfo->tlsRequired && $scheme !== 'tls' && !$this->options->tls) { + $this->transport->upgradeTls([ + 'cafile' => $this->options->tlsCaFile, + 'local_cert' => $this->options->tlsCertFile, + 'local_pk' => $this->options->tlsKeyFile, + ]); + } + + // Send CONNECT + $connectPayload = $this->buildConnectPayload(); + $this->transport->write($this->writer->connect($connectPayload)); + + // Send PING and wait for PONG to confirm connection + $this->transport->write($this->writer->ping()); + + // Read until we get PONG (skip +OK if verbose) + $deadline = microtime(true) + $this->options->connectTimeout; + while (true) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + throw new TimeoutException('Connection handshake timed out'); + } + + [$op, $data] = $this->parser->next($remaining); + + if ($op === ServerOp::Pong) { + break; + } + if ($op === ServerOp::Err) { + $this->transport->close(); + $errMsg = is_string($data) ? $data : 'Unknown error'; + if (stripos($errMsg, 'authorization') !== false || stripos($errMsg, 'authentication') !== false) { + throw new AuthenticationException("Server error: {$errMsg}"); + } + throw new ConnectionException("Server error: {$errMsg}"); + } + // Skip +OK + } + + $this->lastPingTime = microtime(true); + } + + private function buildConnectPayload(): array + { + $payload = [ + 'verbose' => $this->options->verbose, + 'pedantic' => $this->options->pedantic, + 'lang' => self::CLIENT_LANG, + 'version' => self::CLIENT_VERSION, + 'protocol' => 1, + 'echo' => $this->options->echo, + 'headers' => true, + 'no_responders' => true, + ]; + + if ($this->options->name !== '') { + $payload['name'] = $this->options->name; + } + + $authFields = $this->auth->authenticate($this->serverInfo->nonce); + $payload = array_merge($payload, $authFields); + + return $payload; + } + + private function dispatchOp(ServerOp $op, mixed $data): ?Message + { + return match ($op) { + ServerOp::Msg, ServerOp::HMsg => $this->handleMessage($data), + ServerOp::Ping => $this->handlePing(), + ServerOp::Pong => $this->handlePong(), + ServerOp::Err => $this->handleError($data), + ServerOp::Ok => null, + ServerOp::Info => $this->handleInfo($data), + }; + } + + private function handleMessage(array $data): ?Message + { + $headers = null; + if (isset($data['headers']) && $data['headers'] !== null) { + $headers = Headers::fromWire($data['headers']); + } + + $msg = new Message( + subject: $data['subject'], + data: $data['payload'], + replyTo: $data['replyTo'], + headers: $headers, + sid: $data['sid'], + ); + + // Check if this is a reply to a pending request (mux inbox) + if ($this->inboxSub !== null && $data['sid'] === $this->inboxSub->sid) { + $token = $this->extractInboxToken($data['subject']); + if ($token !== null && isset($this->pendingRequests[$token])) { + $this->pendingRequests[$token] = ['message' => $msg, 'resolved' => true]; + return $msg; + } + } + + // Dispatch to subscription + $sub = $this->subscriptions[$data['sid']] ?? null; + if ($sub !== null && $sub->isActive()) { + $sub->deliver($msg); + + // Clean up auto-unsubscribed subscriptions + if (!$sub->isActive()) { + unset($this->subscriptions[$data['sid']]); + } + } + + return $msg; + } + + private function handlePing(): null + { + $this->send($this->writer->pong()); + return null; + } + + private function handlePong(): null + { + $this->outstandingPings = max(0, $this->outstandingPings - 1); + return null; + } + + private function handleError(mixed $data): never + { + $message = is_string($data) ? $data : 'Unknown server error'; + + if ($this->options->onError !== null) { + ($this->options->onError)(new NatsException($message)); + } + + if (stripos($message, 'permissions violation') !== false) { + throw new PermissionException($message); + } + if (stripos($message, 'authorization') !== false || stripos($message, 'authentication') !== false) { + throw new AuthenticationException($message); + } + if (stripos($message, 'maximum payload') !== false) { + throw new MaxPayloadException($message); + } + + throw new ProtocolException("Server error: {$message}"); + } + + private function handleInfo(mixed $data): null + { + if (is_array($data)) { + $this->serverInfo = ServerInfo::fromArray($data); + } + return null; + } + + private function checkPings(): void + { + if ($this->status !== self::STATUS_CONNECTED) { + return; + } + + $now = microtime(true); + if (($now - $this->lastPingTime) >= $this->options->pingInterval) { + if ($this->outstandingPings >= $this->options->maxPingsOut) { + // Stale connection + if ($this->options->allowReconnect) { + $this->attemptReconnect(); + return; + } + throw new ConnectionException('Stale connection: too many outstanding pings'); + } + + try { + $this->send($this->writer->ping()); + $this->outstandingPings++; + $this->lastPingTime = $now; + } catch (ConnectionException) { + if ($this->options->allowReconnect) { + $this->attemptReconnect(); + } + } + } + } + + private function attemptReconnect(): void + { + if ($this->status === self::STATUS_CLOSED || $this->status === self::STATUS_RECONNECTING) { + return; + } + + $this->status = self::STATUS_RECONNECTING; + + if ($this->options->onDisconnect !== null) { + ($this->options->onDisconnect)(); + } + + if (isset($this->transport)) { + $this->transport->close(); + } + + for ($attempt = 0; $attempt < $this->options->maxReconnectAttempts; $attempt++) { + // Wait with jitter before reconnecting + if ($attempt > 0) { + $wait = $this->options->reconnectWait + (lcg_value() * $this->options->reconnectJitter); + usleep((int) ($wait * 1_000_000)); + } + + foreach ($this->serverPool as $index => $url) { + try { + $this->connectToServer($url); + $this->currentServerIndex = $index; + $this->status = self::STATUS_CONNECTED; + $this->reconnectAttempts = 0; + $this->outstandingPings = 0; + + // Re-subscribe all active subscriptions + foreach ($this->subscriptions as $sub) { + if ($sub->isActive()) { + $this->send($this->writer->sub($sub->subject, $sub->sid, $sub->queue)); + } + } + + // Flush any buffered publishes + foreach ($this->pendingBuffer as $cmd) { + $this->send($cmd); + } + $this->pendingBuffer = []; + + if ($this->options->onReconnect !== null) { + ($this->options->onReconnect)(); + } + + return; + } catch (\Throwable) { + continue; + } + } + } + + $this->status = self::STATUS_DISCONNECTED; + throw new ConnectionException('Failed to reconnect to any NATS server'); + } + + private function ensureConnected(): void + { + if ($this->status !== self::STATUS_CONNECTED && $this->status !== self::STATUS_DRAINING) { + throw new ConnectionException("Not connected (status: {$this->status})"); + } + } + + private function send(string $data): void + { + if ($this->status === self::STATUS_RECONNECTING) { + $this->pendingBuffer[] = $data; + return; + } + + try { + $this->transport->write($data); + } catch (ConnectionException $e) { + if ($this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { + $this->pendingBuffer[] = $data; + $this->attemptReconnect(); + return; + } + throw $e; + } + } + + private function ensureInboxSub(): void + { + if ($this->inboxSub !== null) { + return; + } + + $this->inboxPrefix = $this->options->inboxPrefix . '.' . Inbox::generateId(); + $this->inboxSub = $this->subscribe($this->inboxPrefix . '.*'); + } + + private function extractInboxToken(string $subject): ?string + { + if (!str_starts_with($subject, $this->inboxPrefix . '.')) { + return null; + } + + return substr($subject, strlen($this->inboxPrefix) + 1); + } + + private function resolveAuthenticator(ConnectionOptions $options): Authenticator + { + if ($options->credentialsFile !== null) { + return new CredentialsAuth($options->credentialsFile); + } + + if ($options->nkey !== null && $options->nkeySeed !== null) { + return new NKeyAuth($options->nkey, $options->nkeySeed); + } + + if ($options->token !== null) { + return new TokenAuth($options->token); + } + + if ($options->user !== null && $options->pass !== null) { + return new UserPassAuth($options->user, $options->pass); + } + + // Check URL for user info + foreach ($options->servers as $url) { + $parsed = parse_url($url); + if (isset($parsed['user'])) { + $user = rawurldecode($parsed['user']); + $pass = isset($parsed['pass']) ? rawurldecode($parsed['pass']) : ''; + + if ($pass !== '') { + return new UserPassAuth($user, $pass); + } + return new TokenAuth($user); + } + } + + return new NoAuth(); + } + + /** + * @return list + */ + private function buildServerPool(ConnectionOptions $options): array + { + $servers = array_map(fn(string $url) => $this->normalizeUrl($url), $options->servers); + + if (!$options->noRandomize && count($servers) > 1) { + shuffle($servers); + } + + return $servers; + } + + private function normalizeUrl(string $url): string + { + if (!preg_match('#^(nats|tls|ws|wss)://#', $url)) { + $url = 'nats://' . $url; + } + return $url; + } + + /** + * @return array{scheme: string, host: string, port: int} + */ + private function parseUrl(string $url): array + { + $parsed = parse_url($url); + if ($parsed === false) { + throw new ConnectionException("Invalid server URL: {$url}"); + } + + return [ + 'scheme' => $parsed['scheme'] ?? 'nats', + 'host' => $parsed['host'] ?? '127.0.0.1', + 'port' => $parsed['port'] ?? 4222, + ]; + } +} diff --git a/src/ConnectionOptions.php b/src/ConnectionOptions.php new file mode 100644 index 00000000000..02a7a2161e5 --- /dev/null +++ b/src/ConnectionOptions.php @@ -0,0 +1,56 @@ + */ + public readonly array $servers; + + /** + * @param string|list $servers + */ + public function __construct( + string|array $servers = 'nats://127.0.0.1:4222', + public readonly string $name = '', + // Auth + public readonly ?string $user = null, + public readonly ?string $pass = null, + public readonly ?string $token = null, + public readonly ?string $nkey = null, + public readonly ?string $nkeySeed = null, + public readonly ?string $credentialsFile = null, + // TLS + public readonly bool $tls = false, + public readonly ?string $tlsCaFile = null, + public readonly ?string $tlsCertFile = null, + public readonly ?string $tlsKeyFile = null, + // Reconnection + public readonly bool $allowReconnect = true, + public readonly int $maxReconnectAttempts = 60, + public readonly float $reconnectWait = 2.0, + public readonly float $reconnectJitter = 0.1, + // Timeouts + public readonly float $connectTimeout = 2.0, + public readonly float $requestTimeout = 5.0, + public readonly float $drainTimeout = 30.0, + // PING/PONG + public readonly float $pingInterval = 120.0, + public readonly int $maxPingsOut = 2, + // Misc + public readonly bool $verbose = false, + public readonly bool $pedantic = false, + public readonly bool $echo = true, + public readonly bool $noRandomize = false, + public readonly string $inboxPrefix = '_INBOX', + // Callbacks + public readonly ?\Closure $onDisconnect = null, + public readonly ?\Closure $onReconnect = null, + public readonly ?\Closure $onClose = null, + public readonly ?\Closure $onError = null, + ) { + $this->servers = is_string($servers) ? [$servers] : $servers; + } +} diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php new file mode 100644 index 00000000000..229b56eb760 --- /dev/null +++ b/src/Exception/AuthenticationException.php @@ -0,0 +1,9 @@ +> */ + private array $headers = []; + private string $status = ''; + private string $description = ''; + + public function set(string $name, string $value): self + { + $this->headers[$name] = [$value]; + return $this; + } + + public function add(string $name, string $value): self + { + $this->headers[$name][] = $value; + return $this; + } + + public function get(string $name): ?string + { + return $this->headers[$name][0] ?? null; + } + + /** @return list */ + public function getAll(string $name): array + { + return $this->headers[$name] ?? []; + } + + public function has(string $name): bool + { + return isset($this->headers[$name]); + } + + public function delete(string $name): self + { + unset($this->headers[$name]); + return $this; + } + + /** @return array> */ + public function all(): array + { + return $this->headers; + } + + public function getStatus(): string + { + return $this->status; + } + + public function setStatus(string $status, string $description = ''): self + { + $this->status = $status; + $this->description = $description; + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function toWire(): string + { + $result = 'NATS/1.0'; + if ($this->status !== '') { + $result .= ' ' . $this->status; + if ($this->description !== '') { + $result .= ' ' . $this->description; + } + } + $result .= "\r\n"; + + foreach ($this->headers as $name => $values) { + foreach ($values as $value) { + $result .= "{$name}: {$value}\r\n"; + } + } + + $result .= "\r\n"; + + return $result; + } + + public static function fromWire(string $raw): self + { + $headers = new self(); + $lines = explode("\r\n", $raw); + + if (empty($lines)) { + throw new ProtocolException('Empty header block'); + } + + // Parse status line: "NATS/1.0" or "NATS/1.0 503" or "NATS/1.0 503 No Responders" + $statusLine = array_shift($lines); + if (!str_starts_with($statusLine, 'NATS/1.0')) { + throw new ProtocolException("Invalid header version: {$statusLine}"); + } + + $remainder = trim(substr($statusLine, 8)); + if ($remainder !== '') { + $spacePos = strpos($remainder, ' '); + if ($spacePos !== false) { + $headers->status = substr($remainder, 0, $spacePos); + $headers->description = substr($remainder, $spacePos + 1); + } else { + $headers->status = $remainder; + } + } + + // Parse header lines + foreach ($lines as $line) { + if ($line === '') { + continue; + } + + $colonPos = strpos($line, ':'); + if ($colonPos === false) { + continue; + } + + $name = substr($line, 0, $colonPos); + $value = ltrim(substr($line, $colonPos + 1)); + $headers->headers[$name][] = $value; + } + + return $headers; + } + + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->headers); + } + + public function count(): int + { + return count($this->headers); + } +} diff --git a/src/Inbox.php b/src/Inbox.php new file mode 100644 index 00000000000..f3d6eea8da1 --- /dev/null +++ b/src/Inbox.php @@ -0,0 +1,29 @@ +apiPrefix}.CONSUMER.MSG.NEXT.{$this->stream}.{$this->getName()}"; + + $payload = json_encode([ + 'batch' => $batch, + 'expires' => StreamConfig::secondsToNanos($timeout), + ], JSON_THROW_ON_ERROR); + + $inbox = $this->conn->newInbox(); + $sub = $this->conn->subscribe($inbox); + + $this->conn->publish($requestSubject, $payload, $inbox); + + $messageBatch = new MessageBatch($this->conn); + $deadline = microtime(true) + $timeout; + + while (count($messageBatch) < $batch) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + break; + } + + $msg = $sub->nextMessage($remaining); + if ($msg === null) { + break; + } + + // Check for status messages (408 = Request Timeout, 404 = No Messages, 409 = Leadership Change) + if ($msg->headers !== null) { + $status = $msg->headers->getStatus(); + if ($status === '408' || $status === '404' || $status === '409') { + break; + } + } + + $messageBatch->addMessage($msg); + } + + $sub->unsubscribe(); + + return $messageBatch; + } + + /** + * Fetch the next single message. + */ + public function next(?float $timeout = null): ?JetStreamMessage + { + $batch = $this->fetch(1, $timeout); + $messages = $batch->getMessages(); + return $messages[0] ?? null; + } + + public function info(bool $refresh = false): ConsumerInfo + { + if ($refresh) { + $subject = "{$this->apiPrefix}.CONSUMER.INFO.{$this->stream}.{$this->getName()}"; + try { + $response = $this->conn->request($subject); + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($data); + $this->info = ConsumerInfo::fromArray($data); + } catch (TimeoutException) { + // Return cached info on timeout + } + } + return $this->info; + } + + public function getName(): string + { + return $this->info->name; + } + + public function getStream(): string + { + return $this->stream; + } +} diff --git a/src/JetStream/ConsumerConfig.php b/src/JetStream/ConsumerConfig.php new file mode 100644 index 00000000000..eb854e86a93 --- /dev/null +++ b/src/JetStream/ConsumerConfig.php @@ -0,0 +1,121 @@ +|null $filterSubjects + */ + public function __construct( + public readonly ?string $name = null, + public readonly ?string $durableName = null, + public readonly ?string $description = null, + public readonly DeliverPolicy $deliverPolicy = DeliverPolicy::All, + public readonly AckPolicy $ackPolicy = AckPolicy::Explicit, + public readonly ?float $ackWait = null, + public readonly ?int $maxDeliver = null, + public readonly ?string $filterSubject = null, + public readonly ?array $filterSubjects = null, + public readonly ReplayPolicy $replayPolicy = ReplayPolicy::Instant, + public readonly ?int $maxWaiting = null, + public readonly ?int $maxAckPending = null, + public readonly ?float $inactiveThreshold = null, + public readonly ?int $optStartSeq = null, + public readonly ?string $optStartTime = null, + public readonly ?int $maxBatch = null, + public readonly ?int $maxBytes = null, + public readonly bool $memStorage = false, + public readonly ?int $numReplicas = null, + ) { + } + + public function toArray(): array + { + $data = [ + 'deliver_policy' => $this->deliverPolicy->value, + 'ack_policy' => $this->ackPolicy->value, + 'replay_policy' => $this->replayPolicy->value, + ]; + + if ($this->name !== null) { + $data['name'] = $this->name; + } + if ($this->durableName !== null) { + $data['durable_name'] = $this->durableName; + } + if ($this->description !== null) { + $data['description'] = $this->description; + } + if ($this->ackWait !== null) { + $data['ack_wait'] = StreamConfig::secondsToNanos($this->ackWait); + } + if ($this->maxDeliver !== null) { + $data['max_deliver'] = $this->maxDeliver; + } + if ($this->filterSubject !== null) { + $data['filter_subject'] = $this->filterSubject; + } + if ($this->filterSubjects !== null) { + $data['filter_subjects'] = $this->filterSubjects; + } + if ($this->maxWaiting !== null) { + $data['max_waiting'] = $this->maxWaiting; + } + if ($this->maxAckPending !== null) { + $data['max_ack_pending'] = $this->maxAckPending; + } + if ($this->inactiveThreshold !== null) { + $data['inactive_threshold'] = StreamConfig::secondsToNanos($this->inactiveThreshold); + } + if ($this->optStartSeq !== null) { + $data['opt_start_seq'] = $this->optStartSeq; + } + if ($this->optStartTime !== null) { + $data['opt_start_time'] = $this->optStartTime; + } + if ($this->maxBatch !== null) { + $data['max_batch'] = $this->maxBatch; + } + if ($this->maxBytes !== null) { + $data['max_bytes'] = $this->maxBytes; + } + if ($this->memStorage) { + $data['mem_storage'] = true; + } + if ($this->numReplicas !== null) { + $data['num_replicas'] = $this->numReplicas; + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + name: $data['name'] ?? null, + durableName: $data['durable_name'] ?? null, + description: $data['description'] ?? null, + deliverPolicy: DeliverPolicy::tryFrom($data['deliver_policy'] ?? '') ?? DeliverPolicy::All, + ackPolicy: AckPolicy::tryFrom($data['ack_policy'] ?? '') ?? AckPolicy::Explicit, + ackWait: isset($data['ack_wait']) ? StreamConfig::nanosToSeconds($data['ack_wait']) : null, + maxDeliver: $data['max_deliver'] ?? null, + filterSubject: $data['filter_subject'] ?? null, + filterSubjects: $data['filter_subjects'] ?? null, + replayPolicy: ReplayPolicy::tryFrom($data['replay_policy'] ?? '') ?? ReplayPolicy::Instant, + maxWaiting: $data['max_waiting'] ?? null, + maxAckPending: $data['max_ack_pending'] ?? null, + inactiveThreshold: isset($data['inactive_threshold']) ? StreamConfig::nanosToSeconds($data['inactive_threshold']) : null, + optStartSeq: $data['opt_start_seq'] ?? null, + optStartTime: $data['opt_start_time'] ?? null, + maxBatch: $data['max_batch'] ?? null, + maxBytes: $data['max_bytes'] ?? null, + memStorage: $data['mem_storage'] ?? false, + numReplicas: $data['num_replicas'] ?? null, + ); + } +} diff --git a/src/JetStream/ConsumerInfo.php b/src/JetStream/ConsumerInfo.php new file mode 100644 index 00000000000..2dff7c81c36 --- /dev/null +++ b/src/JetStream/ConsumerInfo.php @@ -0,0 +1,34 @@ +apiPrefix = $apiPrefix; + } elseif ($domain !== null) { + $this->apiPrefix = "\$JS.{$domain}.API"; + } else { + $this->apiPrefix = '$JS.API'; + } + } + + // --- Stream Management --- + + public function createStream(StreamConfig $config): Stream + { + $data = $this->apiRequest("STREAM.CREATE.{$config->name}", $config->toArray()); + return new Stream($this, StreamInfo::fromArray($data)); + } + + public function updateStream(StreamConfig $config): Stream + { + $data = $this->apiRequest("STREAM.UPDATE.{$config->name}", $config->toArray()); + return new Stream($this, StreamInfo::fromArray($data)); + } + + public function createOrUpdateStream(StreamConfig $config): Stream + { + try { + return $this->updateStream($config); + } catch (JetStreamException $e) { + if ($e->apiError !== null && $e->apiError->code === 404) { + return $this->createStream($config); + } + throw $e; + } + } + + public function deleteStream(string $name): void + { + $this->apiRequest("STREAM.DELETE.{$name}"); + } + + public function getStream(string $name): Stream + { + $info = $this->getStreamInfo($name); + return new Stream($this, $info); + } + + public function getStreamInfo(string $name): StreamInfo + { + $data = $this->apiRequest("STREAM.INFO.{$name}"); + return StreamInfo::fromArray($data); + } + + /** @return list */ + public function getStreamNames(?string $subject = null): array + { + $payload = $subject !== null ? ['subject' => $subject] : null; + $data = $this->apiRequest('STREAM.NAMES', $payload); + return $data['streams'] ?? []; + } + + /** @return list */ + public function listStreams(?string $subject = null): array + { + $payload = $subject !== null ? ['subject' => $subject] : null; + $data = $this->apiRequest('STREAM.LIST', $payload); + return array_map( + fn(array $s) => StreamInfo::fromArray($s), + $data['streams'] ?? [], + ); + } + + public function purgeStream(string $name, ?string $subject = null): void + { + $payload = $subject !== null ? ['filter' => $subject] : null; + $this->apiRequest("STREAM.PURGE.{$name}", $payload); + } + + // --- Consumer Management --- + + public function createConsumer(string $stream, ConsumerConfig $config): Consumer + { + $consumerName = $config->name ?? $config->durableName; + + if ($consumerName !== null) { + $subject = "CONSUMER.CREATE.{$stream}.{$consumerName}"; + } else { + $subject = "CONSUMER.CREATE.{$stream}"; + } + + $payload = [ + 'stream_name' => $stream, + 'config' => $config->toArray(), + ]; + + $data = $this->apiRequest($subject, $payload); + return new Consumer($this->conn, $stream, ConsumerInfo::fromArray($data), $this->apiPrefix); + } + + public function updateConsumer(string $stream, ConsumerConfig $config): Consumer + { + return $this->createConsumer($stream, $config); + } + + public function deleteConsumer(string $stream, string $consumer): void + { + $this->apiRequest("CONSUMER.DELETE.{$stream}.{$consumer}"); + } + + public function getConsumer(string $stream, string $consumer): Consumer + { + $data = $this->apiRequest("CONSUMER.INFO.{$stream}.{$consumer}"); + return new Consumer($this->conn, $stream, ConsumerInfo::fromArray($data), $this->apiPrefix); + } + + /** @return list */ + public function getConsumerNames(string $stream): array + { + $data = $this->apiRequest("CONSUMER.NAMES.{$stream}"); + return $data['consumers'] ?? []; + } + + // --- Publishing --- + + public function publish(string $subject, string $data = '', ?Headers $headers = null, ?string $msgId = null, ?string $expectedLastMsgId = null, ?int $expectedLastSeq = null, ?int $expectedLastSubjectSeq = null, ?string $expectedStream = null): PubAck + { + $headers ??= new Headers(); + + if ($msgId !== null) { + $headers->set('Nats-Msg-Id', $msgId); + } + if ($expectedLastMsgId !== null) { + $headers->set('Nats-Expected-Last-Msg-Id', $expectedLastMsgId); + } + if ($expectedLastSeq !== null) { + $headers->set('Nats-Expected-Last-Sequence', (string) $expectedLastSeq); + } + if ($expectedLastSubjectSeq !== null) { + $headers->set('Nats-Expected-Last-Subject-Sequence', (string) $expectedLastSubjectSeq); + } + if ($expectedStream !== null) { + $headers->set('Nats-Expected-Stream', $expectedStream); + } + + $useHeaders = count($headers) > 0 ? $headers : null; + $response = $this->conn->request($subject, $data, headers: $useHeaders); + + $responseData = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + self::checkError($responseData); + + return PubAck::fromArray($responseData); + } + + // --- Key-Value --- + + public function createKeyValue(KeyValue\KeyValueConfig $config): KeyValue\KeyValue + { + $streamConfig = $config->toStreamConfig(); + $this->createOrUpdateStream($streamConfig); + return new KeyValue\KeyValue($this->conn, $this, $config->bucket); + } + + public function getKeyValue(string $bucket): KeyValue\KeyValue + { + // Verify the KV stream exists + $this->getStreamInfo("KV_{$bucket}"); + return new KeyValue\KeyValue($this->conn, $this, $bucket); + } + + public function deleteKeyValue(string $bucket): void + { + $this->deleteStream("KV_{$bucket}"); + } + + // --- Account Info --- + + public function accountInfo(): array + { + return $this->apiRequest('INFO'); + } + + // --- Internal --- + + /** + * @return array + */ + private function apiRequest(string $subject, ?array $payload = null, ?float $timeout = null): array + { + $fullSubject = "{$this->apiPrefix}.{$subject}"; + $body = $payload !== null ? json_encode($payload, JSON_THROW_ON_ERROR) : ''; + + $response = $this->conn->request($fullSubject, $body, $timeout); + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + + self::checkError($data); + + return $data; + } + + /** + * @throws JetStreamException + */ + public static function checkError(array $data): void + { + if (isset($data['error'])) { + $error = ApiError::fromArray($data['error']); + throw new JetStreamException( + $error->description, + $error->code, + apiError: $error, + ); + } + } +} diff --git a/src/JetStream/JetStreamMessage.php b/src/JetStream/JetStreamMessage.php new file mode 100644 index 00000000000..31bebf57938 --- /dev/null +++ b/src/JetStream/JetStreamMessage.php @@ -0,0 +1,85 @@ +respond(''); + } + + public function nak(?float $delay = null): void + { + if ($delay !== null) { + $nanos = StreamConfig::secondsToNanos($delay); + $this->respond("-NAK {\"delay\":{$nanos}}"); + } else { + $this->respond('-NAK'); + } + } + + public function inProgress(): void + { + $this->respond('+WPI'); + } + + public function term(?string $reason = null): void + { + if ($reason !== null) { + $this->respond("+TERM {$reason}"); + } else { + $this->respond('+TERM'); + } + } + + public function metadata(): MsgMetadata + { + if ($this->metadata === null) { + if ($this->message->replyTo === null) { + throw new \RuntimeException('Message has no reply subject for metadata parsing'); + } + $this->metadata = MsgMetadata::fromReplySubject($this->message->replyTo); + } + + return $this->metadata; + } + + public function getData(): string + { + return $this->message->data; + } + + public function getSubject(): string + { + return $this->message->subject; + } + + public function getHeaders(): ?Headers + { + return $this->message->headers; + } + + private function respond(string $data): void + { + if ($this->message->replyTo === null) { + throw new \RuntimeException('Cannot acknowledge: message has no reply subject'); + } + + $this->conn->publish($this->message->replyTo, $data); + } +} diff --git a/src/JetStream/MessageBatch.php b/src/JetStream/MessageBatch.php new file mode 100644 index 00000000000..9bad6ac69d0 --- /dev/null +++ b/src/JetStream/MessageBatch.php @@ -0,0 +1,40 @@ + */ + private array $messages = []; + + public function __construct( + private readonly Connection $conn, + ) { + } + + public function addMessage(Message $msg): void + { + $this->messages[] = new JetStreamMessage($this->conn, $msg); + } + + /** @return list */ + public function getMessages(): array + { + return $this->messages; + } + + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->messages); + } + + public function count(): int + { + return count($this->messages); + } +} diff --git a/src/JetStream/MsgMetadata.php b/src/JetStream/MsgMetadata.php new file mode 100644 index 00000000000..70063233295 --- /dev/null +++ b/src/JetStream/MsgMetadata.php @@ -0,0 +1,60 @@ +...... + * Or with domain: $JS.ACK......... + */ + public static function fromReplySubject(string $reply): self + { + $parts = explode('.', $reply); + + // Standard format: $JS.ACK....... + if (count($parts) >= 9 && $parts[0] === '$JS' && $parts[1] === 'ACK') { + if (count($parts) === 9) { + return new self( + stream: $parts[2], + consumer: $parts[3], + numDelivered: (int) $parts[4], + streamSequence: (int) $parts[5], + consumerSequence: (int) $parts[6], + timestamp: $parts[7], + numPending: (int) $parts[8], + ); + } + + // Domain format has extra parts + if (count($parts) >= 11) { + return new self( + domain: $parts[2], + stream: $parts[4], + consumer: $parts[5], + numDelivered: (int) $parts[6], + streamSequence: (int) $parts[7], + consumerSequence: (int) $parts[8], + timestamp: $parts[9], + numPending: (int) $parts[10], + ); + } + } + + throw new \InvalidArgumentException("Cannot parse JetStream reply subject: {$reply}"); + } +} diff --git a/src/JetStream/PubAck.php b/src/JetStream/PubAck.php new file mode 100644 index 00000000000..52f81ef3f25 --- /dev/null +++ b/src/JetStream/PubAck.php @@ -0,0 +1,26 @@ +info->config->name; + } + + public function getConfig(): StreamConfig + { + return $this->info->config; + } + + public function getState(): StreamState + { + return $this->info->state; + } + + public function info(bool $refresh = false): StreamInfo + { + if ($refresh) { + $this->info = $this->js->getStreamInfo($this->getName()); + } + return $this->info; + } + + public function createConsumer(ConsumerConfig $config): Consumer + { + return $this->js->createConsumer($this->getName(), $config); + } + + public function getConsumer(string $name): Consumer + { + return $this->js->getConsumer($this->getName(), $name); + } + + public function deleteConsumer(string $name): void + { + $this->js->deleteConsumer($this->getName(), $name); + } + + public function purge(?string $subject = null): void + { + $this->js->purgeStream($this->getName(), $subject); + } + + public function delete(): void + { + $this->js->deleteStream($this->getName()); + } +} diff --git a/src/JetStream/StreamConfig.php b/src/JetStream/StreamConfig.php new file mode 100644 index 00000000000..d65d3f8e4b4 --- /dev/null +++ b/src/JetStream/StreamConfig.php @@ -0,0 +1,115 @@ + $subjects + * @param float|null $maxAge Max age in seconds (converted to nanoseconds for wire) + * @param float|null $duplicateWindow Duplicate window in seconds + */ + public function __construct( + public readonly string $name, + public readonly array $subjects = [], + public readonly ?string $description = null, + public readonly RetentionPolicy $retention = RetentionPolicy::Limits, + public readonly int $maxConsumers = -1, + public readonly int $maxMsgs = -1, + public readonly int $maxBytes = -1, + public readonly int $maxMsgsPerSubject = -1, + public readonly ?int $maxMsgSize = null, + public readonly ?float $maxAge = null, + public readonly StorageType $storage = StorageType::File, + public readonly int $replicas = 1, + public readonly DiscardPolicy $discard = DiscardPolicy::Old, + public readonly bool $noAck = false, + public readonly ?float $duplicateWindow = null, + public readonly bool $allowDirect = false, + public readonly bool $mirrorDirect = false, + public readonly bool $sealed = false, + public readonly bool $denyDelete = false, + public readonly bool $denyPurge = false, + public readonly bool $allowRollup = false, + ) { + } + + public function toArray(): array + { + $data = [ + 'name' => $this->name, + 'retention' => $this->retention->value, + 'max_consumers' => $this->maxConsumers, + 'max_msgs' => $this->maxMsgs, + 'max_bytes' => $this->maxBytes, + 'max_msgs_per_subject' => $this->maxMsgsPerSubject, + 'storage' => $this->storage->value, + 'num_replicas' => $this->replicas, + 'discard' => $this->discard->value, + 'no_ack' => $this->noAck, + 'allow_direct' => $this->allowDirect, + 'mirror_direct' => $this->mirrorDirect, + 'sealed' => $this->sealed, + 'deny_delete' => $this->denyDelete, + 'deny_purge' => $this->denyPurge, + 'allow_rollup_hdrs' => $this->allowRollup, + ]; + + if (!empty($this->subjects)) { + $data['subjects'] = $this->subjects; + } + if ($this->description !== null) { + $data['description'] = $this->description; + } + if ($this->maxMsgSize !== null) { + $data['max_msg_size'] = $this->maxMsgSize; + } + if ($this->maxAge !== null) { + $data['max_age'] = self::secondsToNanos($this->maxAge); + } + if ($this->duplicateWindow !== null) { + $data['duplicate_window'] = self::secondsToNanos($this->duplicateWindow); + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + name: $data['name'] ?? '', + subjects: $data['subjects'] ?? [], + description: $data['description'] ?? null, + retention: RetentionPolicy::tryFrom($data['retention'] ?? '') ?? RetentionPolicy::Limits, + maxConsumers: $data['max_consumers'] ?? -1, + maxMsgs: $data['max_msgs'] ?? -1, + maxBytes: $data['max_bytes'] ?? -1, + maxMsgsPerSubject: $data['max_msgs_per_subject'] ?? -1, + maxMsgSize: $data['max_msg_size'] ?? null, + maxAge: isset($data['max_age']) ? self::nanosToSeconds($data['max_age']) : null, + storage: StorageType::tryFrom($data['storage'] ?? '') ?? StorageType::File, + replicas: $data['num_replicas'] ?? 1, + discard: DiscardPolicy::tryFrom($data['discard'] ?? '') ?? DiscardPolicy::Old, + noAck: $data['no_ack'] ?? false, + duplicateWindow: isset($data['duplicate_window']) ? self::nanosToSeconds($data['duplicate_window']) : null, + allowDirect: $data['allow_direct'] ?? false, + mirrorDirect: $data['mirror_direct'] ?? false, + sealed: $data['sealed'] ?? false, + denyDelete: $data['deny_delete'] ?? false, + denyPurge: $data['deny_purge'] ?? false, + allowRollup: $data['allow_rollup_hdrs'] ?? false, + ); + } + + public static function secondsToNanos(float $seconds): int + { + return (int) ($seconds * 1_000_000_000); + } + + public static function nanosToSeconds(int $nanos): float + { + return $nanos / 1_000_000_000; + } +} diff --git a/src/JetStream/StreamInfo.php b/src/JetStream/StreamInfo.php new file mode 100644 index 00000000000..dbd12ba3974 --- /dev/null +++ b/src/JetStream/StreamInfo.php @@ -0,0 +1,24 @@ +validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + + try { + $msg = $this->conn->request("\$JS.API.DIRECT.GET.KV_{$this->bucket}", json_encode([ + 'last_by_subj' => $subject, + ], JSON_THROW_ON_ERROR)); + } catch (\Throwable) { + throw new KeyValueException("Key not found: {$key}"); + } + + // Check for delete/purge markers + if ($msg->headers !== null) { + $op = $msg->headers->get('KV-Operation'); + if ($op === 'DEL' || $op === 'PURGE') { + throw new KeyValueException("Key not found: {$key}"); + } + } + + $revision = 0; + $created = null; + if ($msg->headers !== null) { + $seqStr = $msg->headers->get('Nats-Sequence'); + if ($seqStr !== null) { + $revision = (int) $seqStr; + } + $created = $msg->headers->get('Nats-Time-Stamp'); + } + + return new KeyValueEntry( + bucket: $this->bucket, + key: $key, + value: $msg->data, + revision: $revision, + created: $created, + operation: KeyValueOperation::Put, + ); + } + + /** + * Put a value, returning the revision number. + */ + public function put(string $key, string $value): int + { + $this->validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + $ack = $this->js->publish($subject, $value); + + return $ack->sequence; + } + + /** + * Create a key only if it does not already exist. + */ + public function create(string $key, string $value): int + { + $this->validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + $headers = new Headers(); + $headers->set('Nats-Expected-Last-Subject-Sequence', '0'); + + try { + $ack = $this->js->publish($subject, $value, $headers); + } catch (\Throwable $e) { + throw new KeyValueException("Key already exists: {$key}", previous: $e); + } + + return $ack->sequence; + } + + /** + * Update a key only if the current revision matches (CAS). + */ + public function update(string $key, string $value, int $revision): int + { + $this->validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + + try { + $ack = $this->js->publish( + $subject, + $value, + expectedLastSubjectSeq: $revision, + ); + } catch (\Throwable $e) { + throw new KeyValueException("Wrong last revision for key: {$key}", previous: $e); + } + + return $ack->sequence; + } + + public function delete(string $key): void + { + $this->validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + $headers = new Headers(); + $headers->set('KV-Operation', 'DEL'); + + $this->js->publish($subject, '', $headers); + } + + public function purge(string $key): void + { + $this->validateKey($key); + + $subject = "\$KV.{$this->bucket}.{$key}"; + $headers = new Headers(); + $headers->set('KV-Operation', 'PURGE'); + $headers->set('Nats-Rollup', 'sub'); + + $this->js->publish($subject, '', $headers); + } + + /** @return list */ + public function keys(): array + { + $streamName = "KV_{$this->bucket}"; + $subject = "\$KV.{$this->bucket}.>"; + + // Use stream subjects to get all keys + try { + $msg = $this->conn->request('$JS.API.STREAM.INFO.' . $streamName, json_encode([ + 'subjects_filter' => $subject, + ], JSON_THROW_ON_ERROR)); + + $data = json_decode($msg->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($data); + + $keys = []; + $prefix = "\$KV.{$this->bucket}."; + $subjects = $data['state']['subjects'] ?? []; + foreach ($subjects as $subj => $count) { + if (str_starts_with($subj, $prefix)) { + $keys[] = substr($subj, strlen($prefix)); + } + } + + return $keys; + } catch (\Throwable) { + return []; + } + } + + public function status(): KeyValueStatus + { + $info = $this->js->getStreamInfo("KV_{$this->bucket}"); + + return new KeyValueStatus( + bucket: $this->bucket, + values: $info->state->messages, + bytes: $info->state->bytes, + history: $info->config->maxMsgsPerSubject, + ttl: $info->config->maxAge, + streamInfo: $info, + ); + } + + public function getBucket(): string + { + return $this->bucket; + } + + private function validateKey(string $key): void + { + if ($key === '' || str_contains($key, ' ') || str_contains($key, '>') || str_contains($key, '*')) { + throw new KeyValueException("Invalid key: {$key}"); + } + } +} diff --git a/src/KeyValue/KeyValueConfig.php b/src/KeyValue/KeyValueConfig.php new file mode 100644 index 00000000000..9082fe3b1ab --- /dev/null +++ b/src/KeyValue/KeyValueConfig.php @@ -0,0 +1,48 @@ +bucket}", + subjects: ["\$KV.{$this->bucket}.>"], + description: $this->description, + retention: RetentionPolicy::Limits, + maxMsgsPerSubject: $this->history, + maxBytes: $this->maxBytes, + maxMsgSize: $this->maxValueSize > 0 ? $this->maxValueSize : null, + maxAge: $this->ttl, + storage: $this->storage, + replicas: $this->replicas, + discard: DiscardPolicy::New, + allowRollup: true, + allowDirect: true, + mirrorDirect: true, + ); + } +} diff --git a/src/KeyValue/KeyValueEntry.php b/src/KeyValue/KeyValueEntry.php new file mode 100644 index 00000000000..b65866b5170 --- /dev/null +++ b/src/KeyValue/KeyValueEntry.php @@ -0,0 +1,18 @@ +readLine($timeout); + $line = rtrim($line, "\r\n"); + + if ($line === '') { + throw new ProtocolException('Empty protocol line received'); + } + + // +OK + if ($line === '+OK') { + return [ServerOp::Ok, null]; + } + + // PING + if ($line === 'PING') { + return [ServerOp::Ping, null]; + } + + // PONG + if ($line === 'PONG') { + return [ServerOp::Pong, null]; + } + + // -ERR 'message' + if (str_starts_with($line, '-ERR')) { + $message = trim(substr($line, 4), " \t'\""); + return [ServerOp::Err, $message]; + } + + // INFO {json} + if (str_starts_with($line, 'INFO ')) { + $json = substr($line, 5); + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + return [ServerOp::Info, $data]; + } + + // MSG [reply-to] <#bytes> + if (str_starts_with($line, 'MSG ')) { + return $this->parseMsg(substr($line, 4)); + } + + // HMSG [reply-to] <#header-bytes> <#total-bytes> + if (str_starts_with($line, 'HMSG ')) { + return $this->parseHmsg(substr($line, 5)); + } + + throw new ProtocolException("Unknown protocol operation: {$line}"); + } + + /** + * Parse MSG: subject sid [reply-to] #bytes + * + * @return array{0: ServerOp, 1: array{subject: string, sid: string, replyTo: ?string, payload: string}} + */ + private function parseMsg(string $args): array + { + $parts = preg_split('/\s+/', trim($args)); + + if ($parts === false || count($parts) < 3 || count($parts) > 4) { + throw new ProtocolException("Invalid MSG line: MSG {$args}"); + } + + if (count($parts) === 3) { + [$subject, $sid, $byteCount] = $parts; + $replyTo = null; + } else { + [$subject, $sid, $replyTo, $byteCount] = $parts; + } + + $bytes = (int) $byteCount; + $payload = $this->readExactly($bytes); + // Consume trailing \r\n + $this->readExactly(2); + + return [ServerOp::Msg, [ + 'subject' => $subject, + 'sid' => $sid, + 'replyTo' => $replyTo, + 'payload' => $payload, + 'headers' => null, + ]]; + } + + /** + * Parse HMSG: subject sid [reply-to] #header-bytes #total-bytes + * + * @return array{0: ServerOp, 1: array{subject: string, sid: string, replyTo: ?string, payload: string, headers: string}} + */ + private function parseHmsg(string $args): array + { + $parts = preg_split('/\s+/', trim($args)); + + if ($parts === false || count($parts) < 4 || count($parts) > 5) { + throw new ProtocolException("Invalid HMSG line: HMSG {$args}"); + } + + if (count($parts) === 4) { + [$subject, $sid, $headerBytes, $totalBytes] = $parts; + $replyTo = null; + } else { + [$subject, $sid, $replyTo, $headerBytes, $totalBytes] = $parts; + } + + $hdrLen = (int) $headerBytes; + $totalLen = (int) $totalBytes; + $payloadLen = $totalLen - $hdrLen; + + if ($payloadLen < 0) { + throw new ProtocolException("Invalid HMSG byte counts: header={$hdrLen}, total={$totalLen}"); + } + + $headerBlock = $this->readExactly($hdrLen); + $payload = $payloadLen > 0 ? $this->readExactly($payloadLen) : ''; + // Consume trailing \r\n + $this->readExactly(2); + + return [ServerOp::HMsg, [ + 'subject' => $subject, + 'sid' => $sid, + 'replyTo' => $replyTo, + 'payload' => $payload, + 'headers' => $headerBlock, + ]]; + } + + private function readLine(?float $timeout = null): string + { + // Check buffer for a complete line first + $pos = strpos($this->buffer, "\n"); + if ($pos !== false) { + $line = substr($this->buffer, 0, $pos + 1); + $this->buffer = substr($this->buffer, $pos + 1); + return $line; + } + + // Read from transport until we get a line + while (true) { + $data = $this->transport->read(65536, $timeout); + $this->buffer .= $data; + + $pos = strpos($this->buffer, "\n"); + if ($pos !== false) { + $line = substr($this->buffer, 0, $pos + 1); + $this->buffer = substr($this->buffer, $pos + 1); + return $line; + } + } + } + + private function readExactly(int $bytes): string + { + while (strlen($this->buffer) < $bytes) { + $data = $this->transport->read(max(65536, $bytes - strlen($this->buffer))); + if ($data === '') { + throw new ProtocolException('Unexpected end of data while reading payload'); + } + $this->buffer .= $data; + } + + $result = substr($this->buffer, 0, $bytes); + $this->buffer = substr($this->buffer, $bytes); + return $result; + } +} diff --git a/src/Protocol/ServerOp.php b/src/Protocol/ServerOp.php new file mode 100644 index 00000000000..cd97543facc --- /dev/null +++ b/src/Protocol/ServerOp.php @@ -0,0 +1,16 @@ +callback = $callback; + $this->pendingMessages = new \SplQueue(); + } + + public function setConnection(Connection $connection): void + { + $this->connection = $connection; + } + + public function nextMessage(?float $timeout = null): ?Message + { + if (!$this->active) { + return null; + } + + // Return from pending queue first + if (!$this->pendingMessages->isEmpty()) { + return $this->pendingMessages->dequeue(); + } + + // Block and process messages until one arrives for this subscription + if ($this->connection === null) { + return null; + } + + $deadline = $timeout !== null ? microtime(true) + $timeout : null; + + while ($this->active) { + $remaining = $deadline !== null ? $deadline - microtime(true) : null; + if ($remaining !== null && $remaining <= 0) { + return null; + } + + $this->connection->processMessage($remaining); + + if (!$this->pendingMessages->isEmpty()) { + return $this->pendingMessages->dequeue(); + } + } + + return null; + } + + public function unsubscribe(?int $afterMessages = null): void + { + if ($this->connection !== null) { + $this->connection->unsubscribe($this, $afterMessages); + } + } + + public function isActive(): bool + { + return $this->active; + } + + public function deliver(Message $msg): void + { + $this->received++; + + if ($this->callback !== null) { + ($this->callback)($msg); + } else { + $this->pendingMessages->enqueue($msg); + } + + if ($this->maxMessages !== null && $this->received >= $this->maxMessages) { + $this->active = false; + } + } + + public function setMaxMessages(int $max): void + { + $this->maxMessages = $max; + if ($this->received >= $max) { + $this->active = false; + } + } + + public function setInactive(): void + { + $this->active = false; + } + + public function getReceived(): int + { + return $this->received; + } + + public function hasCallback(): bool + { + return $this->callback !== null; + } +} diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php new file mode 100644 index 00000000000..c43e0b83905 --- /dev/null +++ b/src/Transport/TcpTransport.php @@ -0,0 +1,175 @@ +setTimeout($stream, $timeout); + $this->stream = $stream; + } + + public function write(string $data): int + { + $stream = $this->ensureConnected(); + $written = @fwrite($stream, $data); + + if ($written === false) { + throw new ConnectionException('Failed to write to socket'); + } + + return $written; + } + + public function read(int $maxBytes, ?float $timeout = null): string + { + $stream = $this->ensureConnected(); + + if ($timeout !== null) { + $this->setTimeout($stream, $timeout); + } + + $data = @fread($stream, $maxBytes); + + if ($data === false) { + if ($this->isTimedOut($stream)) { + throw new TimeoutException('Read timed out'); + } + throw new ConnectionException('Failed to read from socket'); + } + + if ($data === '' && feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + + return $data; + } + + public function readLine(?float $timeout = null): string + { + $stream = $this->ensureConnected(); + + if ($timeout !== null) { + $this->setTimeout($stream, $timeout); + } + + $line = @fgets($stream); + + if ($line === false) { + if ($this->isTimedOut($stream)) { + throw new TimeoutException('Read timed out'); + } + if (feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + throw new ConnectionException('Failed to read line from socket'); + } + + return $line; + } + + public function upgradeTls(array $options): void + { + $stream = $this->ensureConnected(); + + $contextOptions = ['ssl' => []]; + + if (isset($options['cafile'])) { + $contextOptions['ssl']['cafile'] = $options['cafile']; + } + if (isset($options['local_cert'])) { + $contextOptions['ssl']['local_cert'] = $options['local_cert']; + } + if (isset($options['local_pk'])) { + $contextOptions['ssl']['local_pk'] = $options['local_pk']; + } + if (isset($options['verify_peer'])) { + $contextOptions['ssl']['verify_peer'] = $options['verify_peer']; + } else { + $contextOptions['ssl']['verify_peer'] = true; + } + if (isset($options['verify_peer_name'])) { + $contextOptions['ssl']['verify_peer_name'] = $options['verify_peer_name']; + } else { + $contextOptions['ssl']['verify_peer_name'] = true; + } + + $context = stream_context_get_options($stream); + $merged = array_merge_recursive($context, $contextOptions); + foreach ($merged as $wrapper => $opts) { + foreach ($opts as $key => $value) { + stream_context_set_option($stream, $wrapper, $key, is_array($value) ? end($value) : $value); + } + } + + $result = @stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT); + + if ($result !== true) { + throw new ConnectionException('Failed to upgrade connection to TLS'); + } + } + + public function isConnected(): bool + { + return $this->stream !== null && !feof($this->stream); + } + + public function close(): void + { + if ($this->stream !== null) { + @fclose($this->stream); + $this->stream = null; + } + } + + /** @return resource */ + private function ensureConnected() + { + if ($this->stream === null) { + throw new ConnectionException('Not connected'); + } + + return $this->stream; + } + + /** @param resource $stream */ + private function setTimeout($stream, float $timeout): void + { + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * 1_000_000); + stream_set_timeout($stream, $seconds, $microseconds); + } + + /** @param resource $stream */ + private function isTimedOut($stream): bool + { + $info = stream_get_meta_data($stream); + return $info['timed_out'] ?? false; + } +} diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php new file mode 100644 index 00000000000..a4a614b7a66 --- /dev/null +++ b/src/Transport/TlsTransport.php @@ -0,0 +1,161 @@ + $this->buildSslOptions()]); + + $stream = @stream_socket_client( + $address, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $context, + ); + + if ($stream === false) { + throw new ConnectionException("Failed to connect to {$address}: [{$errno}] {$errstr}"); + } + + stream_set_blocking($stream, true); + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * 1_000_000); + stream_set_timeout($stream, $seconds, $microseconds); + + $this->stream = $stream; + } + + public function write(string $data): int + { + $stream = $this->ensureConnected(); + $written = @fwrite($stream, $data); + + if ($written === false) { + throw new ConnectionException('Failed to write to TLS socket'); + } + + return $written; + } + + public function read(int $maxBytes, ?float $timeout = null): string + { + $stream = $this->ensureConnected(); + + if ($timeout !== null) { + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * 1_000_000); + stream_set_timeout($stream, $seconds, $microseconds); + } + + $data = @fread($stream, $maxBytes); + + if ($data === false) { + $info = stream_get_meta_data($stream); + if ($info['timed_out'] ?? false) { + throw new TimeoutException('Read timed out'); + } + throw new ConnectionException('Failed to read from TLS socket'); + } + + if ($data === '' && feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + + return $data; + } + + public function readLine(?float $timeout = null): string + { + $stream = $this->ensureConnected(); + + if ($timeout !== null) { + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * 1_000_000); + stream_set_timeout($stream, $seconds, $microseconds); + } + + $line = @fgets($stream); + + if ($line === false) { + $info = stream_get_meta_data($stream); + if ($info['timed_out'] ?? false) { + throw new TimeoutException('Read timed out'); + } + if (feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + throw new ConnectionException('Failed to read line from TLS socket'); + } + + return $line; + } + + public function upgradeTls(array $options): void + { + // Already TLS, nothing to do + } + + public function isConnected(): bool + { + return $this->stream !== null && !feof($this->stream); + } + + public function close(): void + { + if ($this->stream !== null) { + @fclose($this->stream); + $this->stream = null; + } + } + + private function buildSslOptions(): array + { + $opts = [ + 'verify_peer' => $this->tlsOptions['verify_peer'] ?? true, + 'verify_peer_name' => $this->tlsOptions['verify_peer_name'] ?? true, + ]; + + if (isset($this->tlsOptions['cafile'])) { + $opts['cafile'] = $this->tlsOptions['cafile']; + } + if (isset($this->tlsOptions['local_cert'])) { + $opts['local_cert'] = $this->tlsOptions['local_cert']; + } + if (isset($this->tlsOptions['local_pk'])) { + $opts['local_pk'] = $this->tlsOptions['local_pk']; + } + + return $opts; + } + + /** @return resource */ + private function ensureConnected() + { + if ($this->stream === null) { + throw new ConnectionException('Not connected'); + } + + return $this->stream; + } +} diff --git a/src/Transport/Transport.php b/src/Transport/Transport.php new file mode 100644 index 00000000000..6357b5bc641 --- /dev/null +++ b/src/Transport/Transport.php @@ -0,0 +1,22 @@ +getServerUrl()); + $this->assertTrue($conn->isConnected()); + $this->assertFalse($conn->isClosed()); + + $info = $conn->getServerInfo(); + $this->assertNotEmpty($info->serverId); + $this->assertNotEmpty($info->version); + + $conn->close(); + $this->assertTrue($conn->isClosed()); + $this->assertFalse($conn->isConnected()); + } + + public function testConnectWithOptions(): void + { + $conn = Connection::connect(new ConnectionOptions( + servers: $this->getServerUrl(), + name: 'test-client', + verbose: false, + )); + + $this->assertTrue($conn->isConnected()); + $conn->close(); + } + + public function testFlush(): void + { + $conn = Connection::connect($this->getServerUrl()); + $conn->flush(); + $this->assertTrue($conn->isConnected()); + $conn->close(); + } +} diff --git a/tests/Integration/PubSubTest.php b/tests/Integration/PubSubTest.php new file mode 100644 index 00000000000..9d2a88deae9 --- /dev/null +++ b/tests/Integration/PubSubTest.php @@ -0,0 +1,130 @@ +getServerUrl()); + + $received = null; + $sub = $conn->subscribe('test.pubsub', function ($msg) use (&$received) { + $received = $msg; + }); + + $conn->publish('test.pubsub', 'hello'); + $conn->processMessage(1.0); + + $this->assertNotNull($received); + $this->assertSame('test.pubsub', $received->subject); + $this->assertSame('hello', $received->data); + + $sub->unsubscribe(); + $conn->close(); + } + + public function testSyncSubscribe(): void + { + $conn = Connection::connect($this->getServerUrl()); + + $sub = $conn->subscribe('test.sync'); + + $conn->publish('test.sync', 'sync-message'); + $msg = $sub->nextMessage(1.0); + + $this->assertNotNull($msg); + $this->assertSame('sync-message', $msg->data); + + $sub->unsubscribe(); + $conn->close(); + } + + public function testWildcard(): void + { + $conn = Connection::connect($this->getServerUrl()); + + $messages = []; + $sub = $conn->subscribe('test.wild.*', function ($msg) use (&$messages) { + $messages[] = $msg; + }); + + $conn->publish('test.wild.one', 'first'); + $conn->publish('test.wild.two', 'second'); + $conn->processMessage(1.0); + $conn->processMessage(1.0); + + $this->assertCount(2, $messages); + + $sub->unsubscribe(); + $conn->close(); + } + + public function testQueueSubscribe(): void + { + $conn = Connection::connect($this->getServerUrl()); + + $count1 = 0; + $count2 = 0; + + $sub1 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count1) { + $count1++; + }); + $sub2 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count2) { + $count2++; + }); + + for ($i = 0; $i < 10; $i++) { + $conn->publish('test.queue', "msg-{$i}"); + } + + for ($i = 0; $i < 10; $i++) { + $conn->processMessage(1.0); + } + + $this->assertSame(10, $count1 + $count2); + + $sub1->unsubscribe(); + $sub2->unsubscribe(); + $conn->close(); + } + + public function testPubSubWithHeaders(): void + { + $conn = Connection::connect($this->getServerUrl()); + + $received = null; + $sub = $conn->subscribe('test.headers', function ($msg) use (&$received) { + $received = $msg; + }); + + $headers = new Headers(); + $headers->set('X-Custom', 'test-value'); + $headers->set('Content-Type', 'text/plain'); + + $conn->publish('test.headers', 'with-headers', headers: $headers); + $conn->processMessage(1.0); + + $this->assertNotNull($received); + $this->assertNotNull($received->headers); + $this->assertSame('test-value', $received->headers->get('X-Custom')); + $this->assertSame('text/plain', $received->headers->get('Content-Type')); + + $sub->unsubscribe(); + $conn->close(); + } +} diff --git a/tests/Integration/RequestReplyTest.php b/tests/Integration/RequestReplyTest.php new file mode 100644 index 00000000000..14127a5aff3 --- /dev/null +++ b/tests/Integration/RequestReplyTest.php @@ -0,0 +1,48 @@ +getServerUrl()); + + // Set up responder + $sub = $conn->subscribe('test.echo', function ($msg) use ($conn) { + if ($msg->replyTo !== null) { + $conn->publish($msg->replyTo, 'echo: ' . $msg->data); + } + }); + + $response = $conn->request('test.echo', 'hello', 2.0); + $this->assertSame('echo: hello', $response->data); + + $sub->unsubscribe(); + $conn->close(); + } + + public function testRequestTimeout(): void + { + $conn = Connection::connect($this->getServerUrl()); + + $this->expectException(TimeoutException::class); + $conn->request('test.no-responder-' . uniqid(), 'hello', 0.5); + + $conn->close(); + } +} diff --git a/tests/Unit/HeadersTest.php b/tests/Unit/HeadersTest.php new file mode 100644 index 00000000000..8cc82ab87bb --- /dev/null +++ b/tests/Unit/HeadersTest.php @@ -0,0 +1,124 @@ +set('X-Foo', 'bar'); + $this->assertSame('bar', $h->get('X-Foo')); + } + + public function testAddMultipleValues(): void + { + $h = new Headers(); + $h->add('X-Multi', 'a'); + $h->add('X-Multi', 'b'); + $this->assertSame('a', $h->get('X-Multi')); + $this->assertSame(['a', 'b'], $h->getAll('X-Multi')); + } + + public function testSetOverwrites(): void + { + $h = new Headers(); + $h->add('X-Key', 'a'); + $h->add('X-Key', 'b'); + $h->set('X-Key', 'c'); + $this->assertSame(['c'], $h->getAll('X-Key')); + } + + public function testHas(): void + { + $h = new Headers(); + $this->assertFalse($h->has('X-Missing')); + $h->set('X-Present', 'yes'); + $this->assertTrue($h->has('X-Present')); + } + + public function testDelete(): void + { + $h = new Headers(); + $h->set('X-Del', 'val'); + $h->delete('X-Del'); + $this->assertFalse($h->has('X-Del')); + } + + public function testCount(): void + { + $h = new Headers(); + $this->assertCount(0, $h); + $h->set('A', '1'); + $h->set('B', '2'); + $this->assertCount(2, $h); + } + + public function testToWire(): void + { + $h = new Headers(); + $h->set('X-Key', 'value'); + $h->set('Content-Type', 'text/plain'); + + $wire = $h->toWire(); + $this->assertStringStartsWith("NATS/1.0\r\n", $wire); + $this->assertStringContainsString("X-Key: value\r\n", $wire); + $this->assertStringContainsString("Content-Type: text/plain\r\n", $wire); + $this->assertStringEndsWith("\r\n\r\n", $wire); + } + + public function testToWireWithStatus(): void + { + $h = new Headers(); + $h->setStatus('503', 'No Responders'); + + $wire = $h->toWire(); + $this->assertStringStartsWith("NATS/1.0 503 No Responders\r\n", $wire); + } + + public function testFromWire(): void + { + $wire = "NATS/1.0\r\nX-Key: value\r\nAnother: test\r\n\r\n"; + $h = Headers::fromWire($wire); + + $this->assertSame('value', $h->get('X-Key')); + $this->assertSame('test', $h->get('Another')); + $this->assertSame('', $h->getStatus()); + } + + public function testFromWireWithStatus(): void + { + $wire = "NATS/1.0 503 No Responders\r\n\r\n"; + $h = Headers::fromWire($wire); + + $this->assertSame('503', $h->getStatus()); + $this->assertSame('No Responders', $h->getDescription()); + } + + public function testFromWireWithStatusOnly(): void + { + $wire = "NATS/1.0 408\r\n\r\n"; + $h = Headers::fromWire($wire); + + $this->assertSame('408', $h->getStatus()); + $this->assertSame('', $h->getDescription()); + } + + public function testRoundTrip(): void + { + $original = new Headers(); + $original->set('Nats-Msg-Id', 'abc-123'); + $original->set('X-Custom', 'test'); + + $wire = $original->toWire(); + $parsed = Headers::fromWire($wire); + + $this->assertSame('abc-123', $parsed->get('Nats-Msg-Id')); + $this->assertSame('test', $parsed->get('X-Custom')); + } +} diff --git a/tests/Unit/InboxTest.php b/tests/Unit/InboxTest.php new file mode 100644 index 00000000000..42954ddb8e2 --- /dev/null +++ b/tests/Unit/InboxTest.php @@ -0,0 +1,38 @@ +assertStringStartsWith('_INBOX.', $inbox); + $this->assertSame(29, strlen($inbox)); // "_INBOX." (7) + 22 chars + } + + public function testCreateWithCustomPrefix(): void + { + $inbox = Inbox::create('MY_INBOX'); + $this->assertStringStartsWith('MY_INBOX.', $inbox); + } + + public function testCreateUnique(): void + { + $inbox1 = Inbox::create(); + $inbox2 = Inbox::create(); + $this->assertNotSame($inbox1, $inbox2); + } + + public function testGenerateId(): void + { + $id = Inbox::generateId(); + $this->assertSame(22, strlen($id)); + $this->assertMatchesRegularExpression('/^[0-9A-Za-z]+$/', $id); + } +} diff --git a/tests/Unit/Protocol/ParserTest.php b/tests/Unit/Protocol/ParserTest.php new file mode 100644 index 00000000000..a297e82e200 --- /dev/null +++ b/tests/Unit/Protocol/ParserTest.php @@ -0,0 +1,174 @@ +pos >= strlen($this->data)) { + return ''; + } + $chunk = substr($this->data, $this->pos, $maxBytes); + $this->pos += strlen($chunk); + return $chunk; + } + + public function readLine(?float $timeout = null): string + { + $nlPos = strpos($this->data, "\n", $this->pos); + if ($nlPos === false) { + return ''; + } + $line = substr($this->data, $this->pos, $nlPos - $this->pos + 1); + $this->pos = $nlPos + 1; + return $line; + } + + public function upgradeTls(array $options): void {} + public function isConnected(): bool { return true; } + public function close(): void {} + }; + + return new Parser($transport); + } + + public function testParseInfo(): void + { + $parser = $this->createParser("INFO {\"server_id\":\"test\",\"version\":\"2.10.0\"}\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Info, $op); + $this->assertSame('test', $data['server_id']); + $this->assertSame('2.10.0', $data['version']); + } + + public function testParsePing(): void + { + $parser = $this->createParser("PING\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Ping, $op); + $this->assertNull($data); + } + + public function testParsePong(): void + { + $parser = $this->createParser("PONG\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Pong, $op); + } + + public function testParseOk(): void + { + $parser = $this->createParser("+OK\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Ok, $op); + } + + public function testParseErr(): void + { + $parser = $this->createParser("-ERR 'Authorization Violation'\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Err, $op); + $this->assertSame('Authorization Violation', $data); + } + + public function testParseMsg(): void + { + $parser = $this->createParser("MSG foo.bar 1 5\r\nhello\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Msg, $op); + $this->assertSame('foo.bar', $data['subject']); + $this->assertSame('1', $data['sid']); + $this->assertNull($data['replyTo']); + $this->assertSame('hello', $data['payload']); + } + + public function testParseMsgWithReply(): void + { + $parser = $this->createParser("MSG foo.bar 1 _INBOX.xyz 5\r\nhello\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Msg, $op); + $this->assertSame('foo.bar', $data['subject']); + $this->assertSame('1', $data['sid']); + $this->assertSame('_INBOX.xyz', $data['replyTo']); + $this->assertSame('hello', $data['payload']); + } + + public function testParseMsgEmpty(): void + { + $parser = $this->createParser("MSG foo 1 0\r\n\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::Msg, $op); + $this->assertSame('', $data['payload']); + } + + public function testParseHmsg(): void + { + $headers = "NATS/1.0\r\nX-Test: value\r\n\r\n"; + $headerLen = strlen($headers); + $payload = 'hello'; + $totalLen = $headerLen + strlen($payload); + $parser = $this->createParser("HMSG foo.bar 1 {$headerLen} {$totalLen}\r\n{$headers}{$payload}\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::HMsg, $op); + $this->assertSame('foo.bar', $data['subject']); + $this->assertSame($headers, $data['headers']); + $this->assertSame('hello', $data['payload']); + } + + public function testParseHmsgWithReply(): void + { + $headers = "NATS/1.0\r\n\r\n"; + $headerLen = strlen($headers); + $totalLen = $headerLen + 3; + $parser = $this->createParser("HMSG foo 1 reply {$headerLen} {$totalLen}\r\n{$headers}bar\r\n"); + [$op, $data] = $parser->next(); + $this->assertSame(ServerOp::HMsg, $op); + $this->assertSame('reply', $data['replyTo']); + $this->assertSame('bar', $data['payload']); + } + + public function testParseMultipleOps(): void + { + $parser = $this->createParser("PING\r\n+OK\r\nMSG foo 1 3\r\nabc\r\n"); + + [$op1,] = $parser->next(); + $this->assertSame(ServerOp::Ping, $op1); + + [$op2,] = $parser->next(); + $this->assertSame(ServerOp::Ok, $op2); + + [$op3, $data3] = $parser->next(); + $this->assertSame(ServerOp::Msg, $op3); + $this->assertSame('abc', $data3['payload']); + } + + public function testParseUnknownOpThrows(): void + { + $parser = $this->createParser("UNKNOWN command\r\n"); + $this->expectException(ProtocolException::class); + $parser->next(); + } +} diff --git a/tests/Unit/Protocol/WriterTest.php b/tests/Unit/Protocol/WriterTest.php new file mode 100644 index 00000000000..144aec19ac3 --- /dev/null +++ b/tests/Unit/Protocol/WriterTest.php @@ -0,0 +1,94 @@ +writer = new Writer(); + } + + public function testConnect(): void + { + $result = $this->writer->connect(['verbose' => false, 'lang' => 'php']); + $this->assertSame("CONNECT {\"verbose\":false,\"lang\":\"php\"}\r\n", $result); + } + + public function testPub(): void + { + $result = $this->writer->pub('foo.bar', 'hello'); + $this->assertSame("PUB foo.bar 5\r\nhello\r\n", $result); + } + + public function testPubEmpty(): void + { + $result = $this->writer->pub('foo', ''); + $this->assertSame("PUB foo 0\r\n\r\n", $result); + } + + public function testPubWithReply(): void + { + $result = $this->writer->pub('foo', 'world', '_INBOX.abc'); + $this->assertSame("PUB foo _INBOX.abc 5\r\nworld\r\n", $result); + } + + public function testHpub(): void + { + $headers = "NATS/1.0\r\nX-Key: value\r\n\r\n"; + $result = $this->writer->hpub('foo', $headers, 'hello'); + $headerLen = strlen($headers); + $totalLen = $headerLen + 5; + $this->assertSame("HPUB foo {$headerLen} {$totalLen}\r\n{$headers}hello\r\n", $result); + } + + public function testHpubWithReply(): void + { + $headers = "NATS/1.0\r\n\r\n"; + $result = $this->writer->hpub('foo', $headers, 'data', 'reply'); + $headerLen = strlen($headers); + $totalLen = $headerLen + 4; + $this->assertSame("HPUB foo reply {$headerLen} {$totalLen}\r\n{$headers}data\r\n", $result); + } + + public function testSub(): void + { + $result = $this->writer->sub('foo.>', '1'); + $this->assertSame("SUB foo.> 1\r\n", $result); + } + + public function testSubWithQueue(): void + { + $result = $this->writer->sub('foo', '5', 'workers'); + $this->assertSame("SUB foo workers 5\r\n", $result); + } + + public function testUnsub(): void + { + $result = $this->writer->unsub('3'); + $this->assertSame("UNSUB 3\r\n", $result); + } + + public function testUnsubWithMax(): void + { + $result = $this->writer->unsub('3', 10); + $this->assertSame("UNSUB 3 10\r\n", $result); + } + + public function testPing(): void + { + $this->assertSame("PING\r\n", $this->writer->ping()); + } + + public function testPong(): void + { + $this->assertSame("PONG\r\n", $this->writer->pong()); + } +} From 0ecd54f163568326a96d6172471839cddfff9e67 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 17 Jun 2026 10:24:05 +0530 Subject: [PATCH 343/470] Update PHP SDK to 26 --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 6dae0b9bc3e..c044be464af 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "5.*", - "appwrite/appwrite": "24.*" + "appwrite/appwrite": "^26.0" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 23ff3abef0b..10f23f32338 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c5a86c0dbd34a49a1f91118323c731c1", + "content-hash": "ef64d20a9770c8a5f5e7b514e5a26df0", "packages": [ { "name": "appwrite/appwrite", - "version": "24.1.0", + "version": "26.0.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "dcb3550a3332de1c1665a015a09e9c73ff515e4f" + "reference": "bc26d0f0cfa4699de12d6af4df70ac0729ab7918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/dcb3550a3332de1c1665a015a09e9c73ff515e4f", - "reference": "dcb3550a3332de1c1665a015a09e9c73ff515e4f", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/bc26d0f0cfa4699de12d6af4df70ac0729ab7918", + "reference": "bc26d0f0cfa4699de12d6af4df70ac0729ab7918", "shasum": "" }, "require": { @@ -27,7 +27,7 @@ }, "require-dev": { "mockery/mockery": "1.6.12", - "phpunit/phpunit": "^10" + "phpunit/phpunit": "^12" }, "type": "library", "autoload": { @@ -43,10 +43,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/24.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/26.0.0", "url": "https://appwrite.io/support" }, - "time": "2026-05-20T09:37:03+00:00" + "time": "2026-06-17T04:32:07+00:00" }, { "name": "brick/math", From 8f5df0274869cf9022eea7d8030ca1a0d458b3b2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 18 Jun 2026 23:18:52 +1200 Subject: [PATCH 344/470] chore(deps): require utopia-php/database ^6.0.0 (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump utopia-php/database to ^6.0.0 (the release that drops the Swoole PDOProxy dependency). Pulls utopia-php/cache to ^3.0 as required by database 6.0; abuse already runs against cache 3.x via appwrite. No code changes — abuse uses the high-level Database API, not PDO::prepare(). Co-authored-by: Claude Opus 4.8 --- composer.json | 2 +- composer.lock | 246 +++++++++++++++++++++++++++++++------------------- 2 files changed, 152 insertions(+), 96 deletions(-) diff --git a/composer.json b/composer.json index c044be464af..418496f475b 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "5.*", + "utopia-php/database": "^6.0.0", "appwrite/appwrite": "^26.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 10f23f32338..90d4ad4eff1 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": "ef64d20a9770c8a5f5e7b514e5a26df0", + "content-hash": "572b903fb7a3498880374104042e73f4", "packages": [ { "name": "appwrite/appwrite", @@ -50,23 +50,22 @@ }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.18.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -98,7 +97,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.18.0" }, "funding": [ { @@ -106,7 +105,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-06-14T18:21:03+00:00" }, { "name": "composer/semver", @@ -1280,20 +1279,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.2", + "version": "4.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": ">=0.8.16 <=0.18", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -1352,9 +1351,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/ramsey/uuid/tree/4.9.3" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2026-06-18T03:57:49+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1429,16 +1428,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.9", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6" + "reference": "e8a112b8415707265a7e614278136a9d92989a6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", - "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/e8a112b8415707265a7e614278136a9d92989a6a", + "reference": "e8a112b8415707265a7e614278136a9d92989a6a", "shasum": "" }, "require": { @@ -1506,7 +1505,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.9" + "source": "https://github.com/symfony/http-client/tree/v7.4.13" }, "funding": [ { @@ -1526,7 +1525,7 @@ "type": "tidelift" } ], - "time": "2026-04-29T13:25:15+00:00" + "time": "2026-05-24T09:57:54+00:00" }, { "name": "symfony/http-client-contracts", @@ -1612,16 +1611,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -1673,7 +1672,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -1693,20 +1692,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php82", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59" + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/34808efe3e68f69685796f7c253a2f1d8ea9df59", - "reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59", + "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", + "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", "shasum": "" }, "require": { @@ -1753,7 +1752,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" }, "funding": [ { @@ -1773,20 +1772,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", "shasum": "" }, "require": { @@ -1833,7 +1832,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" }, "funding": [ { @@ -1853,20 +1852,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-27T06:51:48+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", "shasum": "" }, "require": { @@ -1913,7 +1912,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" }, "funding": [ { @@ -1933,7 +1932,7 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:10:57+00:00" + "time": "2026-05-26T02:25:22+00:00" }, { "name": "symfony/service-contracts", @@ -2076,23 +2075,24 @@ }, { "name": "utopia-php/cache", - "version": "1.0.2", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "d36f9050c39c02e09a7763389c9e71258e74af1f" + "reference": "086687d7ae23dd1dae67b943161e8cef143539e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/d36f9050c39c02e09a7763389c9e71258e74af1f", - "reference": "d36f9050c39c02e09a7763389c9e71258e74af1f", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/086687d7ae23dd1dae67b943161e8cef143539e1", + "reference": "086687d7ae23dd1dae67b943161e8cef143539e1", "shasum": "" }, "require": { "ext-json": "*", "ext-memcached": "*", "ext-redis": "*", - "php": ">=8.0", + "php": ">=8.3", + "utopia-php/circuit-breaker": "0.3.*", "utopia-php/pools": "1.*", "utopia-php/telemetry": "*" }, @@ -2100,6 +2100,7 @@ "laravel/pint": "1.2.*", "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3", + "swoole/ide-helper": "^6.0", "vimeo/psalm": "4.13.1" }, "type": "library", @@ -2122,9 +2123,71 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/1.0.2" + "source": "https://github.com/utopia-php/cache/tree/3.0.2" }, - "time": "2026-05-08T11:40:20+00:00" + "time": "2026-05-19T22:38:16+00:00" + }, + { + "name": "utopia-php/circuit-breaker", + "version": "0.3.1", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/circuit-breaker.git", + "reference": "db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828", + "reference": "db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "laravel/pint": "^1.29", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^10.0", + "utopia-php/telemetry": "^0.4" + }, + "suggest": { + "ext-opentelemetry": "Required by utopia-php/telemetry when using OpenTelemetry metrics.", + "ext-protobuf": "Required by utopia-php/telemetry when using OpenTelemetry metrics.", + "ext-redis": "Required when using Utopia\\CircuitBreaker\\Adapter\\Redis with the phpredis extension.", + "ext-swoole": "Required when using Utopia\\CircuitBreaker\\Adapter\\SwooleTable.", + "utopia-php/telemetry": "Required when passing telemetry adapters or running the local telemetry demo." + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\CircuitBreaker\\": "src/CircuitBreaker" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Team Appwrite", + "email": "team@appwrite.io" + } + ], + "description": "Light & simple Circuit Breaker for PHP to prevent cascading failures in distributed systems.", + "keywords": [ + "circuit-breaker", + "fault-tolerance", + "framework", + "php", + "resilience", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/circuit-breaker/issues", + "source": "https://github.com/utopia-php/circuit-breaker/tree/0.3.1" + }, + "time": "2026-05-29T12:12:23+00:00" }, { "name": "utopia-php/console", @@ -2176,16 +2239,16 @@ }, { "name": "utopia-php/database", - "version": "5.7.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "eb35e68f7f90932d5a60bd72e70158ae7a4e0511" + "reference": "fff9f0effbd40359ff925741ff9424856d8b4fde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/eb35e68f7f90932d5a60bd72e70158ae7a4e0511", - "reference": "eb35e68f7f90932d5a60bd72e70158ae7a4e0511", + "url": "https://api.github.com/repos/utopia-php/database/zipball/fff9f0effbd40359ff925741ff9424856d8b4fde", + "reference": "fff9f0effbd40359ff925741ff9424856d8b4fde", "shasum": "" }, "require": { @@ -2194,7 +2257,7 @@ "ext-pdo": "*", "ext-redis": "*", "php": ">=8.4", - "utopia-php/cache": "1.*", + "utopia-php/cache": "^3.0", "utopia-php/console": "0.1.*", "utopia-php/mongo": "1.*", "utopia-php/pools": "1.*", @@ -2230,22 +2293,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/5.7.0" + "source": "https://github.com/utopia-php/database/tree/6.0.0" }, - "time": "2026-05-06T01:04:08+00:00" + "time": "2026-06-18T10:37:40+00:00" }, { "name": "utopia-php/mongo", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "73593682deee4696525a04e26524c1c1226e1530" + "reference": "0b0aac1cd2772bcafed2d4540d74c15cac9a2d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/73593682deee4696525a04e26524c1c1226e1530", - "reference": "73593682deee4696525a04e26524c1c1226e1530", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/0b0aac1cd2772bcafed2d4540d74c15cac9a2d44", + "reference": "0b0aac1cd2772bcafed2d4540d74c15cac9a2d44", "shasum": "" }, "require": { @@ -2291,34 +2354,34 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/1.1.0" + "source": "https://github.com/utopia-php/mongo/tree/1.2.0" }, - "time": "2026-04-24T06:15:10+00:00" + "time": "2026-06-07T13:02:18+00:00" }, { "name": "utopia-php/pools", - "version": "1.0.3", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10" + "reference": "e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/74de7c5457a2c447f27e7ec4d72e8412a7d68c10", - "reference": "74de7c5457a2c447f27e7ec4d72e8412a7d68c10", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b", + "reference": "e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b", "shasum": "" }, "require": { "php": ">=8.4", - "utopia-php/telemetry": "*" + "utopia-php/telemetry": "^0.4" }, "require-dev": { - "laravel/pint": "1.*", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "11.*", "swoole/ide-helper": "6.*" }, + "suggest": { + "ext-swoole": "Required to use the Swoole pool adapter" + }, "type": "library", "autoload": { "psr-4": { @@ -2344,22 +2407,22 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/1.0.3" + "source": "https://github.com/utopia-php/pools/tree/1.0.5" }, - "time": "2026-02-26T08:42:40+00:00" + "time": "2026-06-10T15:14:31+00:00" }, { "name": "utopia-php/telemetry", - "version": "0.3.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "62bbadad03e593b071b8ca63fac2c117c1900991" + "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/62bbadad03e593b071b8ca63fac2c117c1900991", - "reference": "62bbadad03e593b071b8ca63fac2c117c1900991", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/028f3a85bb987b1e9357fb1632aa40f020d1b86e", + "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e", "shasum": "" }, "require": { @@ -2372,10 +2435,7 @@ "symfony/http-client": "7.*" }, "require-dev": { - "laravel/pint": "1.*", "phpbench/phpbench": "1.*", - "phpstan/phpstan": "2.*", - "phpunit/phpunit": "11.*", "swoole/ide-helper": "6.*" }, "suggest": { @@ -2392,6 +2452,7 @@ "license": [ "MIT" ], + "description": "A lite & fast telemetry library, with adapters for OpenTelemetry", "keywords": [ "framework", "php", @@ -2399,32 +2460,27 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.3.0" + "source": "https://github.com/utopia-php/telemetry/tree/0.4.1" }, - "time": "2026-04-01T13:52:56+00:00" + "time": "2026-06-10T15:48:26+00:00" }, { "name": "utopia-php/validators", - "version": "0.2.2", + "version": "0.2.6", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6" + "reference": "f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", - "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b", + "reference": "f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b", "shasum": "" }, "require": { "php": ">=8.0" }, - "require-dev": { - "laravel/pint": "1.*", - "phpstan/phpstan": "2.*", - "phpunit/phpunit": "11.*" - }, "type": "library", "autoload": { "psr-4": { @@ -2444,9 +2500,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.2.2" + "source": "https://github.com/utopia-php/validators/tree/0.2.6" }, - "time": "2026-04-27T16:30:24+00:00" + "time": "2026-06-10T14:45:13+00:00" } ], "packages-dev": [ From 3b527181546d6191ac2d028a2fd1e500ca90f999 Mon Sep 17 00:00:00 2001 From: premtsd-code Date: Fri, 19 Jun 2026 08:18:23 +0100 Subject: [PATCH 345/470] Add no-op and Redis pool time limit adapters (#115) --- .github/workflows/tests.yml | 4 +- Dockerfile.php-8.5 | 27 +++ composer.json | 3 +- composer.lock | 181 +++++++++-------- docker-compose.yml | 2 +- src/Abuse/Adapters/TimeLimit/None.php | 60 ++++++ src/Abuse/Adapters/TimeLimit/RedisPool.php | 215 +++++++++++++++++++++ tests/Abuse/NoneTest.php | 38 ++++ tests/Abuse/RedisPoolClusterTest.php | 112 +++++++++++ tests/Abuse/RedisPoolTest.php | 106 ++++++++++ 10 files changed, 659 insertions(+), 89 deletions(-) create mode 100644 Dockerfile.php-8.5 create mode 100644 src/Abuse/Adapters/TimeLimit/None.php create mode 100644 src/Abuse/Adapters/TimeLimit/RedisPool.php create mode 100644 tests/Abuse/NoneTest.php create mode 100644 tests/Abuse/RedisPoolClusterTest.php create mode 100644 tests/Abuse/RedisPoolTest.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3ab375cc57a..fec77ce3b8d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.2', '8.3', '8.4'] + php-versions: ['8.4', '8.5'] steps: - name: Checkout repository @@ -31,4 +31,4 @@ jobs: sleep 10 - name: Run Tests - run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml \ No newline at end of file + run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml diff --git a/Dockerfile.php-8.5 b/Dockerfile.php-8.5 new file mode 100644 index 00000000000..19e69648beb --- /dev/null +++ b/Dockerfile.php-8.5 @@ -0,0 +1,27 @@ +FROM composer:2.0 as step0 + +WORKDIR /src/ + +COPY composer.lock /src/ +COPY composer.json /src/ + +RUN composer install --ignore-platform-reqs --optimize-autoloader \ + --no-plugins --no-scripts --prefer-dist + +FROM appwrite/utopia-base:php-8.5-2.1.0 as final + +LABEL maintainer="team@appwrite.io" + +RUN docker-php-ext-install pdo_mysql + +WORKDIR /code + +COPY --from=step0 /src/vendor /code/vendor + +# Add Source Code +COPY ./tests /code/tests +COPY ./src /code/src +COPY ./phpunit.xml /code/phpunit.xml +COPY ./phpbench.json /code/phpbench.json + +CMD [ "tail", "-f", "/dev/null" ] diff --git a/composer.json b/composer.json index 418496f475b..aa29144739e 100755 --- a/composer.json +++ b/composer.json @@ -18,11 +18,12 @@ "bench": "vendor/bin/phpbench run --report=aggregate" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "^6.0.0", + "utopia-php/pools": "1.*", "appwrite/appwrite": "^26.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 90d4ad4eff1..1c988fbb58c 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": "572b903fb7a3498880374104042e73f4", + "content-hash": "bb890a101e6cacd0317970c692b16083", "packages": [ { "name": "appwrite/appwrite", @@ -2731,16 +2731,16 @@ }, { "name": "laravel/pint", - "version": "v1.29.1", + "version": "v1.29.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80" + "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80", - "reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80", + "url": "https://api.github.com/repos/laravel/pint/zipball/da1d1111a6aa2e082d2a388b194afe1ba0a05d14", + "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14", "shasum": "" }, "require": { @@ -2751,14 +2751,14 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.95.1", - "illuminate/view": "^12.56.0", - "larastan/larastan": "^3.9.6", + "friendsofphp/php-cs-fixer": "^3.95.8", + "illuminate/view": "^12.62.0", + "larastan/larastan": "^3.10.0", "laravel-zero/framework": "^12.1.0", + "laravel/agent-detector": "^2.0.2", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.4.0", - "pestphp/pest": "^3.8.6", - "shipfastlabs/agent-detector": "^1.1.3" + "pestphp/pest": "^3.8.6" }, "bin": [ "builds/pint" @@ -2795,7 +2795,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-04-20T15:26:14+00:00" + "time": "2026-06-16T15:34:04+00:00" }, { "name": "myclabs/deep-copy", @@ -3086,16 +3086,16 @@ }, { "name": "phpbench/phpbench", - "version": "1.6.1", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/phpbench/phpbench.git", - "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461" + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/661c8c6abbc7734986cf7bc6062c237fbb450461", - "reference": "661c8c6abbc7734986cf7bc6062c237fbb450461", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", + "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", "shasum": "" }, "require": { @@ -3120,14 +3120,14 @@ "require-dev": { "dantleech/invoke": "^2.0", "ergebnis/composer-normalize": "^2.39", - "jangregor/phpstan-prophecy": "^1.0", + "jangregor/phpstan-prophecy": "^2.0", "php-cs-fixer/shim": "^3.9", "phpspec/prophecy": "^1.22", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^11.5", - "rector/rector": "^1.2", + "rector/rector": "^2.2", "sebastian/exporter": "^6.3.2", "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" @@ -3173,7 +3173,7 @@ ], "support": { "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.6.1" + "source": "https://github.com/phpbench/phpbench/tree/1.7.0" }, "funding": [ { @@ -3181,7 +3181,7 @@ "type": "github" } ], - "time": "2026-03-22T10:27:20+00:00" + "time": "2026-06-08T19:09:20+00:00" }, { "name": "phpstan/phpstan", @@ -4728,16 +4728,16 @@ }, { "name": "seld/jsonlint", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", - "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", "shasum": "" }, "require": { @@ -4776,7 +4776,7 @@ ], "support": { "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" }, "funding": [ { @@ -4788,27 +4788,33 @@ "type": "tidelift" } ], - "time": "2024-07-11T14:55:45+00:00" + "time": "2026-06-12T11:32:29+00:00" }, { "name": "symfony/console", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "7113778e2e91f4709cb3194a75dfa9c0d028d94d" + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/7113778e2e91f4709cb3194a75dfa9c0d028d94d", - "reference": "7113778e2e91f4709cb3194a75dfa9c0d028d94d", + "url": "https://api.github.com/repos/symfony/console/zipball/f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", + "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4|^8.0" + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -4816,14 +4822,18 @@ "require-dev": { "psr/log": "^1|^2|^3", "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", "symfony/lock": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", @@ -4858,7 +4868,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.9" + "source": "https://github.com/symfony/console/tree/v8.1.0" }, "funding": [ { @@ -4878,24 +4888,25 @@ "type": "tidelift" } ], - "time": "2026-04-29T15:02:55+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/filesystem", - "version": "v8.0.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40" + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", - "reference": "d1ec4543d5c6c2dac78503c2fae5ea0b3608ce40", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", + "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, @@ -4928,7 +4939,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.9" + "source": "https://github.com/symfony/filesystem/tree/v8.1.0" }, "funding": [ { @@ -4948,24 +4959,24 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:51:42+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/finder", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8da41214757b87d97f181e3d14a4179286151007" + "reference": "58d2e767a66052c1487356f953445634a8194c64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", - "reference": "8da41214757b87d97f181e3d14a4179286151007", + "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", + "reference": "58d2e767a66052c1487356f953445634a8194c64", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/filesystem": "^7.4|^8.0" @@ -4996,7 +5007,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.8" + "source": "https://github.com/symfony/finder/tree/v8.1.0" }, "funding": [ { @@ -5016,24 +5027,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/options-resolver", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8" + "reference": "88f9c561f678a02d54b897014049fa839e33ff82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b48bce0a70b914f6953dafbd10474df232ed4de8", - "reference": "b48bce0a70b914f6953dafbd10474df232ed4de8", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", + "reference": "88f9c561f678a02d54b897014049fa839e33ff82", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -5067,7 +5078,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v8.0.8" + "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" }, "funding": [ { @@ -5087,7 +5098,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/polyfill-ctype", @@ -5174,16 +5185,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { @@ -5232,7 +5243,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -5252,20 +5263,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -5317,7 +5328,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -5337,24 +5348,24 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/process", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc" + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", - "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -5382,7 +5393,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.8" + "source": "https://github.com/symfony/process/tree/v8.1.0" }, "funding": [ { @@ -5402,24 +5413,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/string", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ae9488f874d7603f9d2dfbf120203882b645d963" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963", - "reference": "ae9488f874d7603f9d2dfbf120203882b645d963", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-grapheme": "^1.33", "symfony/polyfill-intl-normalizer": "^1.0", @@ -5472,7 +5483,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.8" + "source": "https://github.com/symfony/string/tree/v8.1.0" }, "funding": [ { @@ -5492,7 +5503,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "theseer/tokenizer", @@ -5600,7 +5611,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.2", + "php": ">=8.4.1", "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*" diff --git a/docker-compose.yml b/docker-compose.yml index d1345c860f6..8583a6cd892 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: tests: build: context: . - dockerfile: Dockerfile.php-${PHP_VERSION:-8.3} + dockerfile: Dockerfile.php-${PHP_VERSION:-8.4} networks: - abuse depends_on: diff --git a/src/Abuse/Adapters/TimeLimit/None.php b/src/Abuse/Adapters/TimeLimit/None.php new file mode 100644 index 00000000000..37629440e9b --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/None.php @@ -0,0 +1,60 @@ +key = $key; + $this->ttl = $seconds; + $now = \time(); + $this->timestamp = (int) ($now - ($now % $seconds)); + $this->limit = $limit; + } + + protected function count(string $key, int $timestamp): int + { + return 0; + } + + protected function hit(string $key, int $timestamp): void + { + } + + protected function set(string $key, int $timestamp, int $value): void + { + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + return []; + } + + /** + * Delete all logs older than $timestamp + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } +} diff --git a/src/Abuse/Adapters/TimeLimit/RedisPool.php b/src/Abuse/Adapters/TimeLimit/RedisPool.php new file mode 100644 index 00000000000..a7a4cc87f20 --- /dev/null +++ b/src/Abuse/Adapters/TimeLimit/RedisPool.php @@ -0,0 +1,215 @@ +|UtopiaPool<\RedisCluster> $pool + */ + public function __construct( + string $key, + int $limit, + int $seconds, + protected UtopiaPool $pool + ) { + $this->key = $key; + $this->ttl = $seconds; + $now = \time(); + $this->timestamp = (int) ($now - ($now % $seconds)); + $this->limit = $limit; + } + + protected function count(string $key, int $timestamp): int + { + if (0 == $this->limit) { + return 0; + } + + if (!\is_null($this->count)) { + return $this->count; + } + + /** @var int $count */ + $count = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($key, $timestamp): int { + $count = $redis->get(Redis::NAMESPACE . '__' . $key . '__' . $timestamp); + + return \is_numeric($count) ? (int) $count : 0; + }); + + $this->count = $count; + + return $this->count; + } + + protected function hit(string $key, int $timestamp): void + { + if (0 == $this->limit) { + return; + } + + $ttl = $this->ttl; + $key = Redis::NAMESPACE . '__' . $key . '__' . $timestamp; + + $this->pool->use(function (\Redis|\RedisCluster $redis) use ($key, $ttl): void { + $redis->multi(); + try { + $redis->incr($key); + $redis->expire($key, $ttl); + $result = $redis->exec(); + } catch (Throwable $th) { + $this->discard($redis); + throw $th; + } + + if (!\is_array($result) || \in_array(false, $result, true)) { + $this->discard($redis); + throw new RuntimeException('Redis transaction failed.'); + } + }); + + $this->count = ($this->count ?? 0) + 1; + } + + protected function set(string $key, int $timestamp, int $value): void + { + $ttl = $this->ttl; + $key = Redis::NAMESPACE . '__' . $key . '__' . $timestamp; + + $this->pool->use(function (\Redis|\RedisCluster $redis) use ($key, $ttl, $value): void { + $redis->multi(); + try { + $redis->set($key, (string) $value); + $redis->expire($key, $ttl); + $result = $redis->exec(); + } catch (Throwable $th) { + $this->discard($redis); + throw $th; + } + + if (!\is_array($result) || \in_array(false, $result, true)) { + $this->discard($redis); + throw new RuntimeException('Redis transaction failed.'); + } + }); + + $this->count = $value; + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + + /** @var array $result */ + $result = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($offset, $limit): array { + if ($redis instanceof \RedisCluster) { + return $this->getRedisClusterLogs($redis, $offset, $limit); + } + + $cursor = null; + $matches = []; + $pattern = Redis::NAMESPACE . '__*'; + + do { + $keys = $redis->scan($cursor, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $redis->get($key); + } + + return $logs; + }); + + return $result; + } + + /** + * Delete all logs older than $timestamp + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } + + private function discard(\Redis|\RedisCluster $redis): void + { + try { + $redis->discard(); + } catch (Throwable) { + } + } + + /** + * @return array + */ + private function getRedisClusterLogs(\RedisCluster $redis, int $offset, int $limit): array + { + $matches = []; + $pattern = Redis::NAMESPACE . '__*'; + + foreach ($redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + } + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $values = $redis->mget($matches); + if (!\is_array($values)) { + return []; + } + + $logs = \array_combine($matches, $values); + if (!\is_array($logs)) { + return []; + } + + return $logs; + } +} diff --git a/tests/Abuse/NoneTest.php b/tests/Abuse/NoneTest.php new file mode 100644 index 00000000000..e68ea2cf343 --- /dev/null +++ b/tests/Abuse/NoneTest.php @@ -0,0 +1,38 @@ +assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + } + + public function testReturnsNoLogsAndCleanupSucceeds(): void + { + $adapter = new None('none-key', 1, 60); + + $this->assertSame([], $adapter->getLogs()); + $this->assertSame(true, $adapter->cleanup(time())); + } + + public function testResetIsNoop(): void + { + $adapter = new None('none-key', 1, 60); + $abuse = new Abuse($adapter); + + $abuse->reset(); + + $this->assertSame(false, $abuse->check()); + } +} diff --git a/tests/Abuse/RedisPoolClusterTest.php b/tests/Abuse/RedisPoolClusterTest.php new file mode 100644 index 00000000000..dbe7eaea3e3 --- /dev/null +++ b/tests/Abuse/RedisPoolClusterTest.php @@ -0,0 +1,112 @@ +|null + */ + protected static ?Pool $pool = null; + + public static function setUpBeforeClass(): void + { + if (isset(self::$pool)) { + return; + } + + self::$pool = new Pool(new Stack(), 'abuse-redis-cluster', 2, function (): \RedisCluster { + return new \RedisCluster(null, [ + 'redis-cluster-0:6379', + 'redis-cluster-1:6379', + 'redis-cluster-2:6379', + 'redis-cluster-3:6379', + ]); + }); + } + + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + /** @var Pool<\RedisCluster> $pool */ + return new AdapterRedisPool('redis-cluster-pool-' . $key, $limit, $seconds, $pool); + } + + public function testGetLogsSupportsNullableLimit(): void + { + $adapter = $this->getAdapter('logs-null-limit', 1, 60); + $abuse = new \Utopia\Abuse\Abuse($adapter); + + $this->assertSame(false, $abuse->check()); + $this->assertNotEmpty($adapter->getLogs(null, null)); + } + + public function testGetLogsAppliesOffset(): void + { + $this->clearRedisClusterPoolLogs(); + $adapter = $this->getAdapter('logs-offset', 1, 60); + + $this->setRedisClusterPoolLog('a', '1'); + $this->setRedisClusterPoolLog('b', '2'); + $this->setRedisClusterPoolLog('c', '3'); + + $logs = $adapter->getLogs(1, 1); + + $this->assertSame(['abuse__redis-cluster-pool-logs-offset-b__1' => '2'], $logs); + } + + public static function tearDownAfterClass(): void + { + if (!isset(self::$pool)) { + return; + } + + self::$pool->use(function (mixed $redis): void { + if ($redis instanceof \RedisCluster) { + $redis->close(); + } + }); + self::$pool = null; + } + + private function clearRedisClusterPoolLogs(): void + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + $pool->use(function (\RedisCluster $redis): void { + foreach ($redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $redis->scan($cursor, $master, 'abuse__*', 100); + /** @phpstan-ignore-next-line */ + if ($keys === false) { + continue; + } + + foreach ($keys as $key) { + $redis->del($key); + } + } while ($cursor > 0); + } + }); + } + + private function setRedisClusterPoolLog(string $key, string $value): void + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + $pool->use(function (\RedisCluster $redis) use ($key, $value): void { + $redis->set('abuse__redis-cluster-pool-logs-offset-' . $key . '__1', $value); + }); + } +} diff --git a/tests/Abuse/RedisPoolTest.php b/tests/Abuse/RedisPoolTest.php new file mode 100644 index 00000000000..c930fc173d0 --- /dev/null +++ b/tests/Abuse/RedisPoolTest.php @@ -0,0 +1,106 @@ +|null + */ + protected static ?Pool $pool = null; + + public static function setUpBeforeClass(): void + { + if (isset(self::$pool)) { + return; + } + + self::$pool = new Pool(new Stack(), 'abuse-redis', 2, function (): \Redis { + $redis = new \Redis(); + $redis->connect('redis', 6379); + + return $redis; + }); + } + + public function getAdapter(string $key, int $limit, int $seconds): TimeLimit + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + /** @var Pool<\Redis> $pool */ + return new AdapterRedisPool('redis-pool-' . $key, $limit, $seconds, $pool); + } + + public function testGetLogsSupportsNullableLimit(): void + { + $adapter = $this->getAdapter('logs-null-limit', 1, 60); + $abuse = new \Utopia\Abuse\Abuse($adapter); + + $this->assertSame(false, $abuse->check()); + $this->assertNotEmpty($adapter->getLogs(null, null)); + } + + public function testGetLogsAppliesOffset(): void + { + $this->clearRedisPoolLogs(); + $adapter = $this->getAdapter('logs-offset', 1, 60); + + $this->setRedisPoolLog('a', '1'); + $this->setRedisPoolLog('b', '2'); + $this->setRedisPoolLog('c', '3'); + + $logs = $adapter->getLogs(1, 1); + + $this->assertSame(['abuse__redis-pool-logs-offset-b__1' => '2'], $logs); + } + + public static function tearDownAfterClass(): void + { + if (!isset(self::$pool)) { + return; + } + + self::$pool->use(function (mixed $redis): void { + if ($redis instanceof \Redis) { + $redis->close(); + } + }); + self::$pool = null; + } + + private function clearRedisPoolLogs(): void + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + $pool->use(function (\Redis $redis): void { + $cursor = null; + do { + $keys = $redis->scan($cursor, 'abuse__*', 100); + if ($keys === false) { + continue; + } + + foreach ($keys as $key) { + $redis->del($key); + } + } while ($cursor > 0); + }); + } + + private function setRedisPoolLog(string $key, string $value): void + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + $pool->use(function (\Redis $redis) use ($key, $value): void { + $redis->set('abuse__redis-pool-logs-offset-' . $key . '__1', $value); + }); + } +} From 19bd0619b8c6b4aaac3ecb81e9861a1b2fb20dfd Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:08:05 +0100 Subject: [PATCH 346/470] refactor: rename package to utopia-php/nats with Utopia\NATS namespace Rename the Composer package from nats/nats to utopia-php/nats and move the root namespace from Nats to Utopia\NATS across src, tests, examples, README, and PSR-4 autoload config. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 30 ++++++++-------- composer.json | 6 ++-- examples/jetstream.php | 8 ++--- examples/publish.php | 4 +-- examples/request_reply.php | 4 +-- examples/subscribe.php | 4 +-- src/Auth/Authenticator.php | 2 +- src/Auth/CredentialsAuth.php | 4 +-- src/Auth/NKeyAuth.php | 4 +-- src/Auth/NoAuth.php | 2 +- src/Auth/TokenAuth.php | 2 +- src/Auth/UserPassAuth.php | 2 +- src/Connection.php | 42 +++++++++++------------ src/ConnectionOptions.php | 2 +- src/Exception/AuthenticationException.php | 2 +- src/Exception/ConnectionException.php | 2 +- src/Exception/JetStreamException.php | 4 +-- src/Exception/KeyValueException.php | 2 +- src/Exception/MaxPayloadException.php | 2 +- src/Exception/NatsException.php | 2 +- src/Exception/PermissionException.php | 2 +- src/Exception/ProtocolException.php | 2 +- src/Exception/TimeoutException.php | 2 +- src/Headers.php | 4 +-- src/Inbox.php | 2 +- src/JetStream/AckPolicy.php | 2 +- src/JetStream/ApiError.php | 2 +- src/JetStream/Consumer.php | 8 ++--- src/JetStream/ConsumerConfig.php | 2 +- src/JetStream/ConsumerInfo.php | 2 +- src/JetStream/DeliverPolicy.php | 2 +- src/JetStream/DiscardPolicy.php | 2 +- src/JetStream/JetStream.php | 8 ++--- src/JetStream/JetStreamMessage.php | 8 ++--- src/JetStream/MessageBatch.php | 6 ++-- src/JetStream/MsgMetadata.php | 2 +- src/JetStream/PubAck.php | 2 +- src/JetStream/ReplayPolicy.php | 2 +- src/JetStream/RetentionPolicy.php | 2 +- src/JetStream/StorageType.php | 2 +- src/JetStream/Stream.php | 2 +- src/JetStream/StreamConfig.php | 2 +- src/JetStream/StreamInfo.php | 2 +- src/JetStream/StreamState.php | 2 +- src/KeyValue/KeyValue.php | 12 +++---- src/KeyValue/KeyValueConfig.php | 10 +++--- src/KeyValue/KeyValueEntry.php | 2 +- src/KeyValue/KeyValueOperation.php | 2 +- src/KeyValue/KeyValueStatus.php | 4 +-- src/Message.php | 2 +- src/Protocol/Command.php | 2 +- src/Protocol/Parser.php | 6 ++-- src/Protocol/ServerOp.php | 2 +- src/Protocol/Writer.php | 2 +- src/ServerInfo.php | 2 +- src/Subscription.php | 2 +- src/Transport/TcpTransport.php | 6 ++-- src/Transport/TlsTransport.php | 6 ++-- src/Transport/Transport.php | 2 +- tests/Integration/ConnectionTest.php | 6 ++-- tests/Integration/PubSubTest.php | 6 ++-- tests/Integration/RequestReplyTest.php | 6 ++-- tests/Unit/HeadersTest.php | 4 +-- tests/Unit/InboxTest.php | 4 +-- tests/Unit/Protocol/ParserTest.php | 10 +++--- tests/Unit/Protocol/WriterTest.php | 4 +-- 66 files changed, 152 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 2028f75287e..57bd1e09102 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ A modern PHP client for [NATS](https://nats.io) messaging system with JetStream ## Installation ```bash -composer require nats/nats +composer require utopia-php/nats ``` ## Quick Start ```php -use Nats\Connection; +use Utopia\NATS\Connection; $conn = Connection::connect('nats://127.0.0.1:4222'); @@ -38,8 +38,8 @@ $conn->wait(); ### Connecting ```php -use Nats\Connection; -use Nats\ConnectionOptions; +use Utopia\NATS\Connection; +use Utopia\NATS\ConnectionOptions; // Simple $conn = Connection::connect('nats://127.0.0.1:4222'); @@ -62,7 +62,7 @@ $conn = Connection::connect('nats://user:pass@127.0.0.1:4222'); ### Publishing ```php -use Nats\Headers; +use Utopia\NATS\Headers; // Simple publish $conn->publish('orders.new', '{"id": 1}'); @@ -132,9 +132,9 @@ $conn->getServerInfo()->version; // Server info ### Streams ```php -use Nats\JetStream\StreamConfig; -use Nats\JetStream\StorageType; -use Nats\JetStream\RetentionPolicy; +use Utopia\NATS\JetStream\StreamConfig; +use Utopia\NATS\JetStream\StorageType; +use Utopia\NATS\JetStream\RetentionPolicy; $js = $conn->jetStream(); @@ -175,9 +175,9 @@ $ack = $js->publish('orders.new', $data, expectedLastSeq: 42); ### Consumers ```php -use Nats\JetStream\ConsumerConfig; -use Nats\JetStream\AckPolicy; -use Nats\JetStream\DeliverPolicy; +use Utopia\NATS\JetStream\ConsumerConfig; +use Utopia\NATS\JetStream\AckPolicy; +use Utopia\NATS\JetStream\DeliverPolicy; // Create a pull consumer $consumer = $js->createConsumer('ORDERS', new ConsumerConfig( @@ -211,8 +211,8 @@ echo "Deliveries: {$meta->numDelivered}\n"; ## Key-Value Store ```php -use Nats\KeyValue\KeyValueConfig; -use Nats\JetStream\StorageType; +use Utopia\NATS\KeyValue\KeyValueConfig; +use Utopia\NATS\JetStream\StorageType; $js = $conn->jetStream(); @@ -252,7 +252,7 @@ echo "Values: {$status->values}, Bytes: {$status->bytes}\n"; ## Authentication ```php -use Nats\ConnectionOptions; +use Utopia\NATS\ConnectionOptions; // User/Password $conn = Connection::connect(new ConnectionOptions( @@ -284,7 +284,7 @@ $conn = Connection::connect(new ConnectionOptions( ## TLS ```php -use Nats\ConnectionOptions; +use Utopia\NATS\ConnectionOptions; // TLS with system CA $conn = Connection::connect(new ConnectionOptions( diff --git a/composer.json b/composer.json index bb493b1195b..48d8cf0b39f 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "nats/nats", + "name": "utopia-php/nats", "description": "Modern PHP client for NATS messaging system", "type": "library", "license": "Apache-2.0", @@ -16,12 +16,12 @@ }, "autoload": { "psr-4": { - "Nats\\": "src/" + "Utopia\\NATS\\": "src/" } }, "autoload-dev": { "psr-4": { - "Nats\\Tests\\": "tests/" + "Utopia\\NATS\\Tests\\": "tests/" } }, "config": { diff --git a/examples/jetstream.php b/examples/jetstream.php index a5a8c0fc04b..fb8860d667f 100644 --- a/examples/jetstream.php +++ b/examples/jetstream.php @@ -4,10 +4,10 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use Nats\Connection; -use Nats\JetStream\AckPolicy; -use Nats\JetStream\ConsumerConfig; -use Nats\JetStream\StreamConfig; +use Utopia\NATS\Connection; +use Utopia\NATS\JetStream\AckPolicy; +use Utopia\NATS\JetStream\ConsumerConfig; +use Utopia\NATS\JetStream\StreamConfig; $conn = Connection::connect('nats://127.0.0.1:4222'); $js = $conn->jetStream(); diff --git a/examples/publish.php b/examples/publish.php index e3a9bd55d0c..356ce08dd83 100644 --- a/examples/publish.php +++ b/examples/publish.php @@ -4,7 +4,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use Nats\Connection; +use Utopia\NATS\Connection; $conn = Connection::connect('nats://127.0.0.1:4222'); @@ -15,7 +15,7 @@ echo "Published to greet.world\n"; // Publish with headers -$headers = new \Nats\Headers(); +$headers = new \Utopia\NATS\Headers(); $headers->set('Content-Type', 'application/json'); $conn->publish('events.user', '{"action":"login","user":"alice"}', headers: $headers); echo "Published to events.user with headers\n"; diff --git a/examples/request_reply.php b/examples/request_reply.php index 1f1ef83e2a3..c84345e52af 100644 --- a/examples/request_reply.php +++ b/examples/request_reply.php @@ -4,8 +4,8 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use Nats\Connection; -use Nats\Message; +use Utopia\NATS\Connection; +use Utopia\NATS\Message; $conn = Connection::connect('nats://127.0.0.1:4222'); diff --git a/examples/subscribe.php b/examples/subscribe.php index dd8974f4471..b9994b3cbd7 100644 --- a/examples/subscribe.php +++ b/examples/subscribe.php @@ -4,8 +4,8 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use Nats\Connection; -use Nats\Message; +use Utopia\NATS\Connection; +use Utopia\NATS\Message; $conn = Connection::connect('nats://127.0.0.1:4222'); diff --git a/src/Auth/Authenticator.php b/src/Auth/Authenticator.php index 389817a3db5..b9ddacbd475 100644 --- a/src/Auth/Authenticator.php +++ b/src/Auth/Authenticator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; interface Authenticator { diff --git a/src/Auth/CredentialsAuth.php b/src/Auth/CredentialsAuth.php index 7596d388484..fa1c12ba0a2 100644 --- a/src/Auth/CredentialsAuth.php +++ b/src/Auth/CredentialsAuth.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; -use Nats\Exception\AuthenticationException; +use Utopia\NATS\Exception\AuthenticationException; final class CredentialsAuth implements Authenticator { diff --git a/src/Auth/NKeyAuth.php b/src/Auth/NKeyAuth.php index 8c1327345bc..0eab07e3fd3 100644 --- a/src/Auth/NKeyAuth.php +++ b/src/Auth/NKeyAuth.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; -use Nats\Exception\AuthenticationException; +use Utopia\NATS\Exception\AuthenticationException; final class NKeyAuth implements Authenticator { diff --git a/src/Auth/NoAuth.php b/src/Auth/NoAuth.php index c5eebd5b439..f2f578b0332 100644 --- a/src/Auth/NoAuth.php +++ b/src/Auth/NoAuth.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; final class NoAuth implements Authenticator { diff --git a/src/Auth/TokenAuth.php b/src/Auth/TokenAuth.php index 471ab3a0077..3b2bc22237d 100644 --- a/src/Auth/TokenAuth.php +++ b/src/Auth/TokenAuth.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; final class TokenAuth implements Authenticator { diff --git a/src/Auth/UserPassAuth.php b/src/Auth/UserPassAuth.php index cee7610d145..682ef51c90e 100644 --- a/src/Auth/UserPassAuth.php +++ b/src/Auth/UserPassAuth.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Auth; +namespace Utopia\NATS\Auth; final class UserPassAuth implements Authenticator { diff --git a/src/Connection.php b/src/Connection.php index 749534773eb..ddf57033894 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -2,27 +2,27 @@ declare(strict_types=1); -namespace Nats; - -use Nats\Auth\Authenticator; -use Nats\Auth\CredentialsAuth; -use Nats\Auth\NKeyAuth; -use Nats\Auth\NoAuth; -use Nats\Auth\TokenAuth; -use Nats\Auth\UserPassAuth; -use Nats\Exception\AuthenticationException; -use Nats\Exception\ConnectionException; -use Nats\Exception\MaxPayloadException; -use Nats\Exception\NatsException; -use Nats\Exception\PermissionException; -use Nats\Exception\ProtocolException; -use Nats\Exception\TimeoutException; -use Nats\Protocol\Parser; -use Nats\Protocol\ServerOp; -use Nats\Protocol\Writer; -use Nats\Transport\TcpTransport; -use Nats\Transport\TlsTransport; -use Nats\Transport\Transport; +namespace Utopia\NATS; + +use Utopia\NATS\Auth\Authenticator; +use Utopia\NATS\Auth\CredentialsAuth; +use Utopia\NATS\Auth\NKeyAuth; +use Utopia\NATS\Auth\NoAuth; +use Utopia\NATS\Auth\TokenAuth; +use Utopia\NATS\Auth\UserPassAuth; +use Utopia\NATS\Exception\AuthenticationException; +use Utopia\NATS\Exception\ConnectionException; +use Utopia\NATS\Exception\MaxPayloadException; +use Utopia\NATS\Exception\NatsException; +use Utopia\NATS\Exception\PermissionException; +use Utopia\NATS\Exception\ProtocolException; +use Utopia\NATS\Exception\TimeoutException; +use Utopia\NATS\Protocol\Parser; +use Utopia\NATS\Protocol\ServerOp; +use Utopia\NATS\Protocol\Writer; +use Utopia\NATS\Transport\TcpTransport; +use Utopia\NATS\Transport\TlsTransport; +use Utopia\NATS\Transport\Transport; final class Connection { diff --git a/src/ConnectionOptions.php b/src/ConnectionOptions.php index 02a7a2161e5..5efdaa306f3 100644 --- a/src/ConnectionOptions.php +++ b/src/ConnectionOptions.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; final class ConnectionOptions { diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php index 229b56eb760..a171a94f31e 100644 --- a/src/Exception/AuthenticationException.php +++ b/src/Exception/AuthenticationException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class AuthenticationException extends NatsException { diff --git a/src/Exception/ConnectionException.php b/src/Exception/ConnectionException.php index ff07bc70862..563d266b041 100644 --- a/src/Exception/ConnectionException.php +++ b/src/Exception/ConnectionException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class ConnectionException extends NatsException { diff --git a/src/Exception/JetStreamException.php b/src/Exception/JetStreamException.php index e11dafdee6d..3018a9c90dc 100644 --- a/src/Exception/JetStreamException.php +++ b/src/Exception/JetStreamException.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; -use Nats\JetStream\ApiError; +use Utopia\NATS\JetStream\ApiError; class JetStreamException extends NatsException { diff --git a/src/Exception/KeyValueException.php b/src/Exception/KeyValueException.php index e6929adf536..0a83ff8df11 100644 --- a/src/Exception/KeyValueException.php +++ b/src/Exception/KeyValueException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class KeyValueException extends NatsException { diff --git a/src/Exception/MaxPayloadException.php b/src/Exception/MaxPayloadException.php index dab0d6dd557..3f79082324e 100644 --- a/src/Exception/MaxPayloadException.php +++ b/src/Exception/MaxPayloadException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class MaxPayloadException extends NatsException { diff --git a/src/Exception/NatsException.php b/src/Exception/NatsException.php index 2746a6d3757..af532e1033c 100644 --- a/src/Exception/NatsException.php +++ b/src/Exception/NatsException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class NatsException extends \RuntimeException { diff --git a/src/Exception/PermissionException.php b/src/Exception/PermissionException.php index 0b4fa10f396..88f8df1b593 100644 --- a/src/Exception/PermissionException.php +++ b/src/Exception/PermissionException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class PermissionException extends NatsException { diff --git a/src/Exception/ProtocolException.php b/src/Exception/ProtocolException.php index da4d6fbc683..2719f5bf216 100644 --- a/src/Exception/ProtocolException.php +++ b/src/Exception/ProtocolException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class ProtocolException extends NatsException { diff --git a/src/Exception/TimeoutException.php b/src/Exception/TimeoutException.php index cd48684dcb9..6ab0a46b0ba 100644 --- a/src/Exception/TimeoutException.php +++ b/src/Exception/TimeoutException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Exception; +namespace Utopia\NATS\Exception; class TimeoutException extends ConnectionException { diff --git a/src/Headers.php b/src/Headers.php index 4321a84153b..4bd4488d7ba 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; -use Nats\Exception\ProtocolException; +use Utopia\NATS\Exception\ProtocolException; final class Headers implements \IteratorAggregate, \Countable { diff --git a/src/Inbox.php b/src/Inbox.php index f3d6eea8da1..2d96638e9fc 100644 --- a/src/Inbox.php +++ b/src/Inbox.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; final class Inbox { diff --git a/src/JetStream/AckPolicy.php b/src/JetStream/AckPolicy.php index bad912ea7ef..caba9de82f0 100644 --- a/src/JetStream/AckPolicy.php +++ b/src/JetStream/AckPolicy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum AckPolicy: string { diff --git a/src/JetStream/ApiError.php b/src/JetStream/ApiError.php index f79d64e0a3c..a34d0a89931 100644 --- a/src/JetStream/ApiError.php +++ b/src/JetStream/ApiError.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class ApiError { diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index 6109bceaacb..172fe4807ea 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; -use Nats\Connection; -use Nats\Exception\TimeoutException; -use Nats\Headers; +use Utopia\NATS\Connection; +use Utopia\NATS\Exception\TimeoutException; +use Utopia\NATS\Headers; final class Consumer { diff --git a/src/JetStream/ConsumerConfig.php b/src/JetStream/ConsumerConfig.php index eb854e86a93..e472b543319 100644 --- a/src/JetStream/ConsumerConfig.php +++ b/src/JetStream/ConsumerConfig.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class ConsumerConfig { diff --git a/src/JetStream/ConsumerInfo.php b/src/JetStream/ConsumerInfo.php index 2dff7c81c36..5d7ba8b2e9d 100644 --- a/src/JetStream/ConsumerInfo.php +++ b/src/JetStream/ConsumerInfo.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class ConsumerInfo { diff --git a/src/JetStream/DeliverPolicy.php b/src/JetStream/DeliverPolicy.php index 4c2de5417e7..7fe33d73488 100644 --- a/src/JetStream/DeliverPolicy.php +++ b/src/JetStream/DeliverPolicy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum DeliverPolicy: string { diff --git a/src/JetStream/DiscardPolicy.php b/src/JetStream/DiscardPolicy.php index 371d64cbfe8..ea5b152bb76 100644 --- a/src/JetStream/DiscardPolicy.php +++ b/src/JetStream/DiscardPolicy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum DiscardPolicy: string { diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index e286647a8e0..3fe426debc2 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; -use Nats\Connection; -use Nats\Exception\JetStreamException; -use Nats\Headers; +use Utopia\NATS\Connection; +use Utopia\NATS\Exception\JetStreamException; +use Utopia\NATS\Headers; final class JetStream { diff --git a/src/JetStream/JetStreamMessage.php b/src/JetStream/JetStreamMessage.php index 31bebf57938..ae84edf8314 100644 --- a/src/JetStream/JetStreamMessage.php +++ b/src/JetStream/JetStreamMessage.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; -use Nats\Connection; -use Nats\Headers; -use Nats\Message; +use Utopia\NATS\Connection; +use Utopia\NATS\Headers; +use Utopia\NATS\Message; final class JetStreamMessage { diff --git a/src/JetStream/MessageBatch.php b/src/JetStream/MessageBatch.php index 9bad6ac69d0..28c6ec0bfcb 100644 --- a/src/JetStream/MessageBatch.php +++ b/src/JetStream/MessageBatch.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; -use Nats\Connection; -use Nats\Message; +use Utopia\NATS\Connection; +use Utopia\NATS\Message; final class MessageBatch implements \IteratorAggregate, \Countable { diff --git a/src/JetStream/MsgMetadata.php b/src/JetStream/MsgMetadata.php index 70063233295..9a08ebecc20 100644 --- a/src/JetStream/MsgMetadata.php +++ b/src/JetStream/MsgMetadata.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class MsgMetadata { diff --git a/src/JetStream/PubAck.php b/src/JetStream/PubAck.php index 52f81ef3f25..54c7c2add41 100644 --- a/src/JetStream/PubAck.php +++ b/src/JetStream/PubAck.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class PubAck { diff --git a/src/JetStream/ReplayPolicy.php b/src/JetStream/ReplayPolicy.php index 86bb2b8cf47..3618124591a 100644 --- a/src/JetStream/ReplayPolicy.php +++ b/src/JetStream/ReplayPolicy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum ReplayPolicy: string { diff --git a/src/JetStream/RetentionPolicy.php b/src/JetStream/RetentionPolicy.php index 58dbd055e86..146e43f122d 100644 --- a/src/JetStream/RetentionPolicy.php +++ b/src/JetStream/RetentionPolicy.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum RetentionPolicy: string { diff --git a/src/JetStream/StorageType.php b/src/JetStream/StorageType.php index 1bc26019fda..6f5336f3d62 100644 --- a/src/JetStream/StorageType.php +++ b/src/JetStream/StorageType.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; enum StorageType: string { diff --git a/src/JetStream/Stream.php b/src/JetStream/Stream.php index f1771629af6..5128bb0946f 100644 --- a/src/JetStream/Stream.php +++ b/src/JetStream/Stream.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class Stream { diff --git a/src/JetStream/StreamConfig.php b/src/JetStream/StreamConfig.php index d65d3f8e4b4..62292bbbf0b 100644 --- a/src/JetStream/StreamConfig.php +++ b/src/JetStream/StreamConfig.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class StreamConfig { diff --git a/src/JetStream/StreamInfo.php b/src/JetStream/StreamInfo.php index dbd12ba3974..043ec24bce8 100644 --- a/src/JetStream/StreamInfo.php +++ b/src/JetStream/StreamInfo.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class StreamInfo { diff --git a/src/JetStream/StreamState.php b/src/JetStream/StreamState.php index 75beac631f7..e519a387f9b 100644 --- a/src/JetStream/StreamState.php +++ b/src/JetStream/StreamState.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\JetStream; +namespace Utopia\NATS\JetStream; final class StreamState { diff --git a/src/KeyValue/KeyValue.php b/src/KeyValue/KeyValue.php index 8d74e712b74..900aba7909c 100644 --- a/src/KeyValue/KeyValue.php +++ b/src/KeyValue/KeyValue.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Nats\KeyValue; +namespace Utopia\NATS\KeyValue; -use Nats\Connection; -use Nats\Exception\KeyValueException; -use Nats\Headers; -use Nats\JetStream\JetStream; -use Nats\JetStream\StreamConfig; +use Utopia\NATS\Connection; +use Utopia\NATS\Exception\KeyValueException; +use Utopia\NATS\Headers; +use Utopia\NATS\JetStream\JetStream; +use Utopia\NATS\JetStream\StreamConfig; final class KeyValue { diff --git a/src/KeyValue/KeyValueConfig.php b/src/KeyValue/KeyValueConfig.php index 9082fe3b1ab..3975795e7e3 100644 --- a/src/KeyValue/KeyValueConfig.php +++ b/src/KeyValue/KeyValueConfig.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Nats\KeyValue; +namespace Utopia\NATS\KeyValue; -use Nats\JetStream\DiscardPolicy; -use Nats\JetStream\RetentionPolicy; -use Nats\JetStream\StorageType; -use Nats\JetStream\StreamConfig; +use Utopia\NATS\JetStream\DiscardPolicy; +use Utopia\NATS\JetStream\RetentionPolicy; +use Utopia\NATS\JetStream\StorageType; +use Utopia\NATS\JetStream\StreamConfig; final class KeyValueConfig { diff --git a/src/KeyValue/KeyValueEntry.php b/src/KeyValue/KeyValueEntry.php index b65866b5170..26f04f608fa 100644 --- a/src/KeyValue/KeyValueEntry.php +++ b/src/KeyValue/KeyValueEntry.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\KeyValue; +namespace Utopia\NATS\KeyValue; final class KeyValueEntry { diff --git a/src/KeyValue/KeyValueOperation.php b/src/KeyValue/KeyValueOperation.php index 0e68eba1d60..d6ca3083fe4 100644 --- a/src/KeyValue/KeyValueOperation.php +++ b/src/KeyValue/KeyValueOperation.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\KeyValue; +namespace Utopia\NATS\KeyValue; enum KeyValueOperation: string { diff --git a/src/KeyValue/KeyValueStatus.php b/src/KeyValue/KeyValueStatus.php index 0e00cf50d61..24ab787608b 100644 --- a/src/KeyValue/KeyValueStatus.php +++ b/src/KeyValue/KeyValueStatus.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\KeyValue; +namespace Utopia\NATS\KeyValue; -use Nats\JetStream\StreamInfo; +use Utopia\NATS\JetStream\StreamInfo; final class KeyValueStatus { diff --git a/src/Message.php b/src/Message.php index 4c2918a437a..d0b1fb11097 100644 --- a/src/Message.php +++ b/src/Message.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; final class Message { diff --git a/src/Protocol/Command.php b/src/Protocol/Command.php index 676dc9b8b35..e20646b062e 100644 --- a/src/Protocol/Command.php +++ b/src/Protocol/Command.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Protocol; +namespace Utopia\NATS\Protocol; enum Command: string { diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index 96935e3fa34..c7a48ee7c29 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Protocol; +namespace Utopia\NATS\Protocol; -use Nats\Exception\ProtocolException; -use Nats\Transport\Transport; +use Utopia\NATS\Exception\ProtocolException; +use Utopia\NATS\Transport\Transport; final class Parser { diff --git a/src/Protocol/ServerOp.php b/src/Protocol/ServerOp.php index cd97543facc..9620a81c9ed 100644 --- a/src/Protocol/ServerOp.php +++ b/src/Protocol/ServerOp.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Protocol; +namespace Utopia\NATS\Protocol; enum ServerOp: string { diff --git a/src/Protocol/Writer.php b/src/Protocol/Writer.php index 39f21acf9cb..5386964cdcf 100644 --- a/src/Protocol/Writer.php +++ b/src/Protocol/Writer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Protocol; +namespace Utopia\NATS\Protocol; final class Writer { diff --git a/src/ServerInfo.php b/src/ServerInfo.php index c3d9c444e84..f591e5486ad 100644 --- a/src/ServerInfo.php +++ b/src/ServerInfo.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; final class ServerInfo { diff --git a/src/Subscription.php b/src/Subscription.php index 610fe685999..135c8d118f4 100644 --- a/src/Subscription.php +++ b/src/Subscription.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats; +namespace Utopia\NATS; final class Subscription { diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index c43e0b83905..bf4a13fd7af 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Transport; +namespace Utopia\NATS\Transport; -use Nats\Exception\ConnectionException; -use Nats\Exception\TimeoutException; +use Utopia\NATS\Exception\ConnectionException; +use Utopia\NATS\Exception\TimeoutException; final class TcpTransport implements Transport { diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php index a4a614b7a66..610e1f583fe 100644 --- a/src/Transport/TlsTransport.php +++ b/src/Transport/TlsTransport.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Transport; +namespace Utopia\NATS\Transport; -use Nats\Exception\ConnectionException; -use Nats\Exception\TimeoutException; +use Utopia\NATS\Exception\ConnectionException; +use Utopia\NATS\Exception\TimeoutException; final class TlsTransport implements Transport { diff --git a/src/Transport/Transport.php b/src/Transport/Transport.php index 6357b5bc641..bb899206c34 100644 --- a/src/Transport/Transport.php +++ b/src/Transport/Transport.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Nats\Transport; +namespace Utopia\NATS\Transport; interface Transport { diff --git a/tests/Integration/ConnectionTest.php b/tests/Integration/ConnectionTest.php index d52d6fa7608..1b1132e2940 100644 --- a/tests/Integration/ConnectionTest.php +++ b/tests/Integration/ConnectionTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Tests\Integration; +namespace Utopia\NATS\Tests\Integration; -use Nats\Connection; -use Nats\ConnectionOptions; +use Utopia\NATS\Connection; +use Utopia\NATS\ConnectionOptions; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Integration/PubSubTest.php b/tests/Integration/PubSubTest.php index 9d2a88deae9..77e6512738e 100644 --- a/tests/Integration/PubSubTest.php +++ b/tests/Integration/PubSubTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Tests\Integration; +namespace Utopia\NATS\Tests\Integration; -use Nats\Connection; -use Nats\Headers; +use Utopia\NATS\Connection; +use Utopia\NATS\Headers; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Integration/RequestReplyTest.php b/tests/Integration/RequestReplyTest.php index 14127a5aff3..a4cdd1d96bf 100644 --- a/tests/Integration/RequestReplyTest.php +++ b/tests/Integration/RequestReplyTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Nats\Tests\Integration; +namespace Utopia\NATS\Tests\Integration; -use Nats\Connection; -use Nats\Exception\TimeoutException; +use Utopia\NATS\Connection; +use Utopia\NATS\Exception\TimeoutException; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Unit/HeadersTest.php b/tests/Unit/HeadersTest.php index 8cc82ab87bb..a6dbb9e4513 100644 --- a/tests/Unit/HeadersTest.php +++ b/tests/Unit/HeadersTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Tests\Unit; +namespace Utopia\NATS\Tests\Unit; -use Nats\Headers; +use Utopia\NATS\Headers; use PHPUnit\Framework\TestCase; final class HeadersTest extends TestCase diff --git a/tests/Unit/InboxTest.php b/tests/Unit/InboxTest.php index 42954ddb8e2..53f02d5737c 100644 --- a/tests/Unit/InboxTest.php +++ b/tests/Unit/InboxTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Tests\Unit; +namespace Utopia\NATS\Tests\Unit; -use Nats\Inbox; +use Utopia\NATS\Inbox; use PHPUnit\Framework\TestCase; final class InboxTest extends TestCase diff --git a/tests/Unit/Protocol/ParserTest.php b/tests/Unit/Protocol/ParserTest.php index a297e82e200..b5f4a130159 100644 --- a/tests/Unit/Protocol/ParserTest.php +++ b/tests/Unit/Protocol/ParserTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Nats\Tests\Unit\Protocol; +namespace Utopia\NATS\Tests\Unit\Protocol; -use Nats\Exception\ProtocolException; -use Nats\Protocol\Parser; -use Nats\Protocol\ServerOp; -use Nats\Transport\Transport; +use Utopia\NATS\Exception\ProtocolException; +use Utopia\NATS\Protocol\Parser; +use Utopia\NATS\Protocol\ServerOp; +use Utopia\NATS\Transport\Transport; use PHPUnit\Framework\TestCase; final class ParserTest extends TestCase diff --git a/tests/Unit/Protocol/WriterTest.php b/tests/Unit/Protocol/WriterTest.php index 144aec19ac3..a63b1914f20 100644 --- a/tests/Unit/Protocol/WriterTest.php +++ b/tests/Unit/Protocol/WriterTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Nats\Tests\Unit\Protocol; +namespace Utopia\NATS\Tests\Unit\Protocol; -use Nats\Protocol\Writer; +use Utopia\NATS\Protocol\Writer; use PHPUnit\Framework\TestCase; final class WriterTest extends TestCase From f152fbf236ff48bdda860048b9ffd26ba267ccb1 Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:28:45 +0100 Subject: [PATCH 347/470] Merge pull request #33 from utopia-php/feat/absorb-nats chore(nats): absorb into the monorepo --- .github/workflows/mirror.yml | 20 + README.md | 3 + composer.json | 20 +- composer.lock | 1678 +---------------- docker-compose.yml | 11 + phpstan.neon | 8 + phpunit.xml | 14 +- src/Auth/CredentialsAuth.php | 2 +- src/Auth/NKeyAuth.php | 12 +- src/Auth/TokenAuth.php | 3 +- src/Auth/UserPassAuth.php | 3 +- src/Connection.php | 26 +- src/ConnectionOptions.php | 2 +- src/Exception/AuthenticationException.php | 4 +- src/Exception/ConnectionException.php | 4 +- src/Exception/KeyValueException.php | 4 +- src/Exception/MaxPayloadException.php | 4 +- src/Exception/NatsException.php | 4 +- src/Exception/PermissionException.php | 4 +- src/Exception/ProtocolException.php | 4 +- src/Exception/TimeoutException.php | 4 +- src/Headers.php | 2 +- src/Inbox.php | 4 +- src/JetStream/ApiError.php | 3 +- src/JetStream/Consumer.php | 6 +- src/JetStream/ConsumerConfig.php | 3 +- src/JetStream/ConsumerInfo.php | 3 +- src/JetStream/JetStream.php | 12 +- src/JetStream/JetStreamMessage.php | 3 +- src/JetStream/MessageBatch.php | 5 +- src/JetStream/MsgMetadata.php | 9 +- src/JetStream/PubAck.php | 3 +- src/JetStream/Stream.php | 3 +- src/JetStream/StreamConfig.php | 3 +- src/JetStream/StreamInfo.php | 3 +- src/JetStream/StreamState.php | 3 +- src/KeyValue/KeyValue.php | 6 +- src/KeyValue/KeyValueConfig.php | 3 +- src/KeyValue/KeyValueEntry.php | 3 +- src/KeyValue/KeyValueStatus.php | 3 +- src/Message.php | 3 +- src/Protocol/Parser.php | 15 +- src/Protocol/Writer.php | 6 +- src/ServerInfo.php | 3 +- src/Subscription.php | 29 +- src/Transport/TcpTransport.php | 4 +- src/Transport/TlsTransport.php | 7 +- tests/{Integration => E2E}/ConnectionTest.php | 4 +- tests/{Integration => E2E}/PubSubTest.php | 4 +- .../{Integration => E2E}/RequestReplyTest.php | 25 +- tests/Unit/HeadersTest.php | 2 +- tests/Unit/InboxTest.php | 6 +- tests/Unit/Protocol/ParserTest.php | 21 +- tests/Unit/Protocol/WriterTest.php | 6 +- 54 files changed, 199 insertions(+), 1850 deletions(-) create mode 100644 .github/workflows/mirror.yml create mode 100644 docker-compose.yml create mode 100644 phpstan.neon rename tests/{Integration => E2E}/ConnectionTest.php (97%) rename tests/{Integration => E2E}/PubSubTest.php (98%) rename tests/{Integration => E2E}/RequestReplyTest.php (59%) diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 00000000000..f5a4c104156 --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,20 @@ +name: Mirror + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + +jobs: + redirect: + runs-on: ubuntu-latest + steps: + - name: Redirect pull request to the monorepo + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.html_url }} + run: | + gh pr comment "$PR" --body "Thanks for contributing! This repository is a read-only mirror — development for this library happens in [\`packages/nats\`](https://github.com/utopia-php/monorepo/tree/main/packages/nats) in the [utopia-php monorepo](https://github.com/utopia-php/monorepo). Please open this pull request there instead." + gh pr close "$PR" diff --git a/README.md b/README.md index 57bd1e09102..f4e03d3ef9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # nats.php +> [!IMPORTANT] +> This repository is a read-only mirror of the [utopia-php monorepo](https://github.com/utopia-php/monorepo). Development happens in [`packages/nats`](https://github.com/utopia-php/monorepo/tree/main/packages/nats) — please open issues and pull requests there. + A modern PHP client for [NATS](https://nats.io) messaging system with JetStream and Key-Value store support. ## Requirements diff --git a/composer.json b/composer.json index 48d8cf0b39f..ad22ec1c1c1 100644 --- a/composer.json +++ b/composer.json @@ -3,14 +3,17 @@ "description": "Modern PHP client for NATS messaging system", "type": "library", "license": "Apache-2.0", - "keywords": ["nats", "messaging", "pubsub", "jetstream", "queue"], + "keywords": [ + "nats", + "messaging", + "pubsub", + "jetstream", + "queue" + ], "require": { "php": ">=8.1", "ext-json": "*" }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, "suggest": { "ext-sodium": "Required for NKey and JWT credential authentication" }, @@ -24,7 +27,14 @@ "Utopia\\NATS\\Tests\\": "tests/" } }, + "scripts": { + "test": "phpunit --testsuite unit", + "test:e2e": "phpunit --testsuite e2e" + }, "config": { + "optimize-autoloader": true, "sort-packages": true - } + }, + "minimum-stability": "stable", + "prefer-stable": true } diff --git a/composer.lock b/composer.lock index bf220be3be9..f757b4b141a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,1685 +4,13 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "85bcd1dc40ee2a844faa03163ff3dda1", + "content-hash": "cfb25afa49eaafa76acee2a192910511", "packages": [], - "packages-dev": [ - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.7.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" - }, - "time": "2025-12-06T11:56:16+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "10.1.16", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^10.1" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:31:57+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "4.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T06:24:48+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:56:09+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-31T14:07:24+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:57:52+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "10.5.63", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "10.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-01-27T05:48:37+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:12:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2023-02-03T06:58:43+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "abandoned": true, - "time": "2023-02-03T06:59:15+00:00" - }, - { - "name": "sebastian/comparator", - "version": "5.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:25:16+00:00" - }, - { - "name": "sebastian/complexity", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:37:17+00:00" - }, - { - "name": "sebastian/diff", - "version": "5.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:15:17+00:00" - }, - { - "name": "sebastian/environment", - "version": "6.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-23T08:47:14+00:00" - }, - { - "name": "sebastian/exporter", - "version": "5.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:09:11+00:00" - }, - { - "name": "sebastian/global-state", - "version": "6.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T07:19:19+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-21T08:38:20+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:08:32+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:06:18+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "5.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2025-08-10T07:50:56+00:00" - }, - { - "name": "sebastian/type", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "phpunit/phpunit": "^10.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T07:10:45+00:00" - }, - { - "name": "sebastian/version", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-07T11:34:05+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-11-17T20:03:58+00:00" - } - ], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=8.1", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..5f5386dc9f2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +services: + nats: + image: nats:2.10-alpine + command: ["-js", "-m", "8222"] + ports: + - "14222:4222" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8222/healthz"] + interval: 2s + timeout: 2s + retries: 30 diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000000..ef0ae2f1823 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +parameters: + level: 5 + paths: + - src + - tests + excludePaths: + - vendor + treatPhpDocTypesAsCertain: false diff --git a/phpunit.xml b/phpunit.xml index b55911d431b..9920c46cf09 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,18 +1,17 @@ + failOnNotice="true"> tests/Unit - - tests/Integration + + tests/E2E @@ -20,4 +19,7 @@ src + + + diff --git a/src/Auth/CredentialsAuth.php b/src/Auth/CredentialsAuth.php index fa1c12ba0a2..9e66daf39ed 100644 --- a/src/Auth/CredentialsAuth.php +++ b/src/Auth/CredentialsAuth.php @@ -56,7 +56,7 @@ private static function extractBetween(string $content, string $begin, string $e return null; } - $startPos += strlen($begin); + $startPos += \strlen($begin); $endPos = strpos($content, $end, $startPos); if ($endPos === false) { return null; diff --git a/src/Auth/NKeyAuth.php b/src/Auth/NKeyAuth.php index 0eab07e3fd3..13b56718f0e 100644 --- a/src/Auth/NKeyAuth.php +++ b/src/Auth/NKeyAuth.php @@ -15,7 +15,7 @@ public function __construct( string $nkey, string $nkeySeed, ) { - if (!function_exists('sodium_crypto_sign_detached')) { + if (!\function_exists('sodium_crypto_sign_detached')) { throw new AuthenticationException('NKey authentication requires the sodium PHP extension'); } @@ -43,7 +43,7 @@ public function authenticate(?string $nonce = null): array private static function decodeSeed(string $seed): string { $decoded = self::base32Decode($seed); - if (strlen($decoded) < 4) { + if (\strlen($decoded) < 4) { throw new AuthenticationException('Invalid NKey seed'); } @@ -59,7 +59,7 @@ private static function base32Decode(string $input): string $buffer = 0; $bitsLeft = 0; - for ($i = 0, $len = strlen($input); $i < $len; $i++) { + for ($i = 0, $len = \strlen($input); $i < $len; $i++) { $val = strpos($alphabet, $input[$i]); if ($val === false) { throw new AuthenticationException('Invalid base32 character in NKey seed'); @@ -69,7 +69,7 @@ private static function base32Decode(string $input): string if ($bitsLeft >= 8) { $bitsLeft -= 8; - $output .= chr(($buffer >> $bitsLeft) & 0xFF); + $output .= \chr(($buffer >> $bitsLeft) & 0xFF); } } @@ -83,8 +83,8 @@ private static function base32Encode(string $input): string $buffer = 0; $bitsLeft = 0; - for ($i = 0, $len = strlen($input); $i < $len; $i++) { - $buffer = ($buffer << 8) | ord($input[$i]); + for ($i = 0, $len = \strlen($input); $i < $len; $i++) { + $buffer = ($buffer << 8) | \ord($input[$i]); $bitsLeft += 8; while ($bitsLeft >= 5) { diff --git a/src/Auth/TokenAuth.php b/src/Auth/TokenAuth.php index 3b2bc22237d..dd6753df207 100644 --- a/src/Auth/TokenAuth.php +++ b/src/Auth/TokenAuth.php @@ -8,8 +8,7 @@ final class TokenAuth implements Authenticator { public function __construct( private readonly string $token, - ) { - } + ) {} public function authenticate(?string $nonce = null): array { diff --git a/src/Auth/UserPassAuth.php b/src/Auth/UserPassAuth.php index 682ef51c90e..c7f52ad0ce8 100644 --- a/src/Auth/UserPassAuth.php +++ b/src/Auth/UserPassAuth.php @@ -9,8 +9,7 @@ final class UserPassAuth implements Authenticator public function __construct( private readonly string $user, private readonly string $pass, - ) { - } + ) {} public function authenticate(?string $nonce = null): array { diff --git a/src/Connection.php b/src/Connection.php index ddf57033894..941ed9b1d2c 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -59,8 +59,6 @@ final class Connection // Reconnection /** @var list */ private array $serverPool = []; - private int $currentServerIndex = 0; - private int $reconnectAttempts = 0; /** @var list */ private array $pendingBuffer = []; @@ -97,9 +95,9 @@ public function publish(string $subject, string $data = '', ?string $replyTo = n { $this->ensureConnected(); - if (isset($this->serverInfo) && strlen($data) > $this->serverInfo->maxPayload) { + if (isset($this->serverInfo) && \strlen($data) > $this->serverInfo->maxPayload) { throw new MaxPayloadException( - "Payload size " . strlen($data) . " exceeds server maximum of {$this->serverInfo->maxPayload}" + 'Payload size ' . \strlen($data) . " exceeds server maximum of {$this->serverInfo->maxPayload}", ); } @@ -353,9 +351,7 @@ private function doConnect(): void foreach ($this->serverPool as $index => $url) { try { $this->connectToServer($url); - $this->currentServerIndex = $index; $this->status = self::STATUS_CONNECTED; - $this->reconnectAttempts = 0; return; } catch (\Throwable $e) { $lastError = $e; @@ -400,7 +396,7 @@ private function connectToServer(string $url): void // Merge connect_urls into server pool foreach ($this->serverInfo->connectUrls as $connectUrl) { - if (!in_array($connectUrl, $this->serverPool, true)) { + if (!\in_array($connectUrl, $this->serverPool, true)) { $this->serverPool[] = $this->normalizeUrl($connectUrl); } } @@ -436,7 +432,7 @@ private function connectToServer(string $url): void } if ($op === ServerOp::Err) { $this->transport->close(); - $errMsg = is_string($data) ? $data : 'Unknown error'; + $errMsg = \is_string($data) ? $data : 'Unknown error'; if (stripos($errMsg, 'authorization') !== false || stripos($errMsg, 'authentication') !== false) { throw new AuthenticationException("Server error: {$errMsg}"); } @@ -483,10 +479,10 @@ private function dispatchOp(ServerOp $op, mixed $data): ?Message }; } - private function handleMessage(array $data): ?Message + private function handleMessage(array $data): Message { $headers = null; - if (isset($data['headers']) && $data['headers'] !== null) { + if (isset($data['headers'])) { $headers = Headers::fromWire($data['headers']); } @@ -535,7 +531,7 @@ private function handlePong(): null private function handleError(mixed $data): never { - $message = is_string($data) ? $data : 'Unknown server error'; + $message = \is_string($data) ? $data : 'Unknown server error'; if ($this->options->onError !== null) { ($this->options->onError)(new NatsException($message)); @@ -556,7 +552,7 @@ private function handleError(mixed $data): never private function handleInfo(mixed $data): null { - if (is_array($data)) { + if (\is_array($data)) { $this->serverInfo = ServerInfo::fromArray($data); } return null; @@ -617,9 +613,7 @@ private function attemptReconnect(): void foreach ($this->serverPool as $index => $url) { try { $this->connectToServer($url); - $this->currentServerIndex = $index; $this->status = self::STATUS_CONNECTED; - $this->reconnectAttempts = 0; $this->outstandingPings = 0; // Re-subscribe all active subscriptions @@ -692,7 +686,7 @@ private function extractInboxToken(string $subject): ?string return null; } - return substr($subject, strlen($this->inboxPrefix) + 1); + return substr($subject, \strlen($this->inboxPrefix) + 1); } private function resolveAuthenticator(ConnectionOptions $options): Authenticator @@ -737,7 +731,7 @@ private function buildServerPool(ConnectionOptions $options): array { $servers = array_map(fn(string $url) => $this->normalizeUrl($url), $options->servers); - if (!$options->noRandomize && count($servers) > 1) { + if (!$options->noRandomize && \count($servers) > 1) { shuffle($servers); } diff --git a/src/ConnectionOptions.php b/src/ConnectionOptions.php index 5efdaa306f3..aedbb294f3c 100644 --- a/src/ConnectionOptions.php +++ b/src/ConnectionOptions.php @@ -51,6 +51,6 @@ public function __construct( public readonly ?\Closure $onClose = null, public readonly ?\Closure $onError = null, ) { - $this->servers = is_string($servers) ? [$servers] : $servers; + $this->servers = \is_string($servers) ? [$servers] : $servers; } } diff --git a/src/Exception/AuthenticationException.php b/src/Exception/AuthenticationException.php index a171a94f31e..6ce5d6737bc 100644 --- a/src/Exception/AuthenticationException.php +++ b/src/Exception/AuthenticationException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class AuthenticationException extends NatsException -{ -} +class AuthenticationException extends NatsException {} diff --git a/src/Exception/ConnectionException.php b/src/Exception/ConnectionException.php index 563d266b041..f9b02e2c516 100644 --- a/src/Exception/ConnectionException.php +++ b/src/Exception/ConnectionException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class ConnectionException extends NatsException -{ -} +class ConnectionException extends NatsException {} diff --git a/src/Exception/KeyValueException.php b/src/Exception/KeyValueException.php index 0a83ff8df11..842c6bbfb50 100644 --- a/src/Exception/KeyValueException.php +++ b/src/Exception/KeyValueException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class KeyValueException extends NatsException -{ -} +class KeyValueException extends NatsException {} diff --git a/src/Exception/MaxPayloadException.php b/src/Exception/MaxPayloadException.php index 3f79082324e..f26f0ff284b 100644 --- a/src/Exception/MaxPayloadException.php +++ b/src/Exception/MaxPayloadException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class MaxPayloadException extends NatsException -{ -} +class MaxPayloadException extends NatsException {} diff --git a/src/Exception/NatsException.php b/src/Exception/NatsException.php index af532e1033c..7ff43af0aab 100644 --- a/src/Exception/NatsException.php +++ b/src/Exception/NatsException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class NatsException extends \RuntimeException -{ -} +class NatsException extends \RuntimeException {} diff --git a/src/Exception/PermissionException.php b/src/Exception/PermissionException.php index 88f8df1b593..6252691b724 100644 --- a/src/Exception/PermissionException.php +++ b/src/Exception/PermissionException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class PermissionException extends NatsException -{ -} +class PermissionException extends NatsException {} diff --git a/src/Exception/ProtocolException.php b/src/Exception/ProtocolException.php index 2719f5bf216..0f52ece1418 100644 --- a/src/Exception/ProtocolException.php +++ b/src/Exception/ProtocolException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class ProtocolException extends NatsException -{ -} +class ProtocolException extends NatsException {} diff --git a/src/Exception/TimeoutException.php b/src/Exception/TimeoutException.php index 6ab0a46b0ba..0dcb1ef472f 100644 --- a/src/Exception/TimeoutException.php +++ b/src/Exception/TimeoutException.php @@ -4,6 +4,4 @@ namespace Utopia\NATS\Exception; -class TimeoutException extends ConnectionException -{ -} +class TimeoutException extends ConnectionException {} diff --git a/src/Headers.php b/src/Headers.php index 4bd4488d7ba..2619430f593 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -144,6 +144,6 @@ public function getIterator(): \ArrayIterator public function count(): int { - return count($this->headers); + return \count($this->headers); } } diff --git a/src/Inbox.php b/src/Inbox.php index 2d96638e9fc..97c7c3ce85d 100644 --- a/src/Inbox.php +++ b/src/Inbox.php @@ -18,10 +18,10 @@ public static function generateId(): string { $id = ''; $bytes = random_bytes(self::ID_LENGTH); - $charsetLen = strlen(self::CHARSET); + $charsetLen = \strlen(self::CHARSET); for ($i = 0; $i < self::ID_LENGTH; $i++) { - $id .= self::CHARSET[ord($bytes[$i]) % $charsetLen]; + $id .= self::CHARSET[\ord($bytes[$i]) % $charsetLen]; } return $id; diff --git a/src/JetStream/ApiError.php b/src/JetStream/ApiError.php index a34d0a89931..f70d9f19950 100644 --- a/src/JetStream/ApiError.php +++ b/src/JetStream/ApiError.php @@ -10,8 +10,7 @@ public function __construct( public readonly int $code, public readonly int $errCode, public readonly string $description, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index 172fe4807ea..e56b170f270 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -6,7 +6,6 @@ use Utopia\NATS\Connection; use Utopia\NATS\Exception\TimeoutException; -use Utopia\NATS\Headers; final class Consumer { @@ -15,8 +14,7 @@ public function __construct( private readonly string $stream, private ConsumerInfo $info, private readonly string $apiPrefix = '$JS.API', - ) { - } + ) {} /** * Fetch a batch of messages from the consumer. @@ -39,7 +37,7 @@ public function fetch(int $batch = 1, ?float $timeout = null): MessageBatch $messageBatch = new MessageBatch($this->conn); $deadline = microtime(true) + $timeout; - while (count($messageBatch) < $batch) { + while (\count($messageBatch) < $batch) { $remaining = $deadline - microtime(true); if ($remaining <= 0) { break; diff --git a/src/JetStream/ConsumerConfig.php b/src/JetStream/ConsumerConfig.php index e472b543319..230202c6060 100644 --- a/src/JetStream/ConsumerConfig.php +++ b/src/JetStream/ConsumerConfig.php @@ -31,8 +31,7 @@ public function __construct( public readonly ?int $maxBytes = null, public readonly bool $memStorage = false, public readonly ?int $numReplicas = null, - ) { - } + ) {} public function toArray(): array { diff --git a/src/JetStream/ConsumerInfo.php b/src/JetStream/ConsumerInfo.php index 5d7ba8b2e9d..de9becc22d4 100644 --- a/src/JetStream/ConsumerInfo.php +++ b/src/JetStream/ConsumerInfo.php @@ -15,8 +15,7 @@ public function __construct( public readonly int $numRedelivered, public readonly int $numWaiting, public readonly int $numPending, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 3fe426debc2..59629a2b441 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -7,6 +7,8 @@ use Utopia\NATS\Connection; use Utopia\NATS\Exception\JetStreamException; use Utopia\NATS\Headers; +use Utopia\NATS\KeyValue\KeyValue; +use Utopia\NATS\KeyValue\KeyValueConfig; final class JetStream { @@ -160,7 +162,7 @@ public function publish(string $subject, string $data = '', ?Headers $headers = $headers->set('Nats-Expected-Stream', $expectedStream); } - $useHeaders = count($headers) > 0 ? $headers : null; + $useHeaders = \count($headers) > 0 ? $headers : null; $response = $this->conn->request($subject, $data, headers: $useHeaders); $responseData = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); @@ -171,18 +173,18 @@ public function publish(string $subject, string $data = '', ?Headers $headers = // --- Key-Value --- - public function createKeyValue(KeyValue\KeyValueConfig $config): KeyValue\KeyValue + public function createKeyValue(KeyValueConfig $config): KeyValue { $streamConfig = $config->toStreamConfig(); $this->createOrUpdateStream($streamConfig); - return new KeyValue\KeyValue($this->conn, $this, $config->bucket); + return new KeyValue($this->conn, $this, $config->bucket); } - public function getKeyValue(string $bucket): KeyValue\KeyValue + public function getKeyValue(string $bucket): KeyValue { // Verify the KV stream exists $this->getStreamInfo("KV_{$bucket}"); - return new KeyValue\KeyValue($this->conn, $this, $bucket); + return new KeyValue($this->conn, $this, $bucket); } public function deleteKeyValue(string $bucket): void diff --git a/src/JetStream/JetStreamMessage.php b/src/JetStream/JetStreamMessage.php index ae84edf8314..118652cb44a 100644 --- a/src/JetStream/JetStreamMessage.php +++ b/src/JetStream/JetStreamMessage.php @@ -15,8 +15,7 @@ final class JetStreamMessage public function __construct( private readonly Connection $conn, public readonly Message $message, - ) { - } + ) {} public function ack(): void { diff --git a/src/JetStream/MessageBatch.php b/src/JetStream/MessageBatch.php index 28c6ec0bfcb..0f37bfec92b 100644 --- a/src/JetStream/MessageBatch.php +++ b/src/JetStream/MessageBatch.php @@ -14,8 +14,7 @@ final class MessageBatch implements \IteratorAggregate, \Countable public function __construct( private readonly Connection $conn, - ) { - } + ) {} public function addMessage(Message $msg): void { @@ -35,6 +34,6 @@ public function getIterator(): \ArrayIterator public function count(): int { - return count($this->messages); + return \count($this->messages); } } diff --git a/src/JetStream/MsgMetadata.php b/src/JetStream/MsgMetadata.php index 9a08ebecc20..1f1717d951a 100644 --- a/src/JetStream/MsgMetadata.php +++ b/src/JetStream/MsgMetadata.php @@ -15,8 +15,7 @@ public function __construct( public readonly string $timestamp, public readonly int $numPending, public readonly ?string $domain = null, - ) { - } + ) {} /** * Parse from reply subject: $JS.ACK....... @@ -27,8 +26,8 @@ public static function fromReplySubject(string $reply): self $parts = explode('.', $reply); // Standard format: $JS.ACK....... - if (count($parts) >= 9 && $parts[0] === '$JS' && $parts[1] === 'ACK') { - if (count($parts) === 9) { + if (\count($parts) >= 9 && $parts[0] === '$JS' && $parts[1] === 'ACK') { + if (\count($parts) === 9) { return new self( stream: $parts[2], consumer: $parts[3], @@ -41,7 +40,7 @@ public static function fromReplySubject(string $reply): self } // Domain format has extra parts - if (count($parts) >= 11) { + if (\count($parts) >= 11) { return new self( domain: $parts[2], stream: $parts[4], diff --git a/src/JetStream/PubAck.php b/src/JetStream/PubAck.php index 54c7c2add41..1ec5dca0d00 100644 --- a/src/JetStream/PubAck.php +++ b/src/JetStream/PubAck.php @@ -11,8 +11,7 @@ public function __construct( public readonly int $sequence, public readonly ?string $domain = null, public readonly bool $duplicate = false, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/JetStream/Stream.php b/src/JetStream/Stream.php index 5128bb0946f..765faa755ee 100644 --- a/src/JetStream/Stream.php +++ b/src/JetStream/Stream.php @@ -9,8 +9,7 @@ final class Stream public function __construct( private readonly JetStream $js, private StreamInfo $info, - ) { - } + ) {} public function getName(): string { diff --git a/src/JetStream/StreamConfig.php b/src/JetStream/StreamConfig.php index 62292bbbf0b..24aa0be8d1b 100644 --- a/src/JetStream/StreamConfig.php +++ b/src/JetStream/StreamConfig.php @@ -33,8 +33,7 @@ public function __construct( public readonly bool $denyDelete = false, public readonly bool $denyPurge = false, public readonly bool $allowRollup = false, - ) { - } + ) {} public function toArray(): array { diff --git a/src/JetStream/StreamInfo.php b/src/JetStream/StreamInfo.php index 043ec24bce8..cbb87ef3829 100644 --- a/src/JetStream/StreamInfo.php +++ b/src/JetStream/StreamInfo.php @@ -10,8 +10,7 @@ public function __construct( public readonly StreamConfig $config, public readonly StreamState $state, public readonly string $created, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/JetStream/StreamState.php b/src/JetStream/StreamState.php index e519a387f9b..448defc86b1 100644 --- a/src/JetStream/StreamState.php +++ b/src/JetStream/StreamState.php @@ -14,8 +14,7 @@ public function __construct( public readonly int $lastSeq, public readonly ?string $lastTs, public readonly int $consumerCount, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/KeyValue/KeyValue.php b/src/KeyValue/KeyValue.php index 900aba7909c..7973ee86aa0 100644 --- a/src/KeyValue/KeyValue.php +++ b/src/KeyValue/KeyValue.php @@ -8,7 +8,6 @@ use Utopia\NATS\Exception\KeyValueException; use Utopia\NATS\Headers; use Utopia\NATS\JetStream\JetStream; -use Utopia\NATS\JetStream\StreamConfig; final class KeyValue { @@ -16,8 +15,7 @@ public function __construct( private readonly Connection $conn, private readonly JetStream $js, private readonly string $bucket, - ) { - } + ) {} public function get(string $key): KeyValueEntry { @@ -159,7 +157,7 @@ public function keys(): array $subjects = $data['state']['subjects'] ?? []; foreach ($subjects as $subj => $count) { if (str_starts_with($subj, $prefix)) { - $keys[] = substr($subj, strlen($prefix)); + $keys[] = substr($subj, \strlen($prefix)); } } diff --git a/src/KeyValue/KeyValueConfig.php b/src/KeyValue/KeyValueConfig.php index 3975795e7e3..242937628a3 100644 --- a/src/KeyValue/KeyValueConfig.php +++ b/src/KeyValue/KeyValueConfig.php @@ -23,8 +23,7 @@ public function __construct( public readonly int $maxBytes = -1, public readonly StorageType $storage = StorageType::File, public readonly int $replicas = 1, - ) { - } + ) {} public function toStreamConfig(): StreamConfig { diff --git a/src/KeyValue/KeyValueEntry.php b/src/KeyValue/KeyValueEntry.php index 26f04f608fa..c5f518e86d9 100644 --- a/src/KeyValue/KeyValueEntry.php +++ b/src/KeyValue/KeyValueEntry.php @@ -13,6 +13,5 @@ public function __construct( public readonly int $revision, public readonly ?string $created = null, public readonly KeyValueOperation $operation = KeyValueOperation::Put, - ) { - } + ) {} } diff --git a/src/KeyValue/KeyValueStatus.php b/src/KeyValue/KeyValueStatus.php index 24ab787608b..a8d5bf3b30d 100644 --- a/src/KeyValue/KeyValueStatus.php +++ b/src/KeyValue/KeyValueStatus.php @@ -15,6 +15,5 @@ public function __construct( public readonly int $history, public readonly ?float $ttl, public readonly StreamInfo $streamInfo, - ) { - } + ) {} } diff --git a/src/Message.php b/src/Message.php index d0b1fb11097..187c87c0f06 100644 --- a/src/Message.php +++ b/src/Message.php @@ -12,6 +12,5 @@ public function __construct( public readonly ?string $replyTo = null, public readonly ?Headers $headers = null, public readonly ?string $sid = null, - ) { - } + ) {} } diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index c7a48ee7c29..7f5db498508 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -13,8 +13,7 @@ final class Parser public function __construct( private readonly Transport $transport, - ) { - } + ) {} /** * Read and parse the next server operation. @@ -80,11 +79,11 @@ private function parseMsg(string $args): array { $parts = preg_split('/\s+/', trim($args)); - if ($parts === false || count($parts) < 3 || count($parts) > 4) { + if ($parts === false || \count($parts) < 3 || \count($parts) > 4) { throw new ProtocolException("Invalid MSG line: MSG {$args}"); } - if (count($parts) === 3) { + if (\count($parts) === 3) { [$subject, $sid, $byteCount] = $parts; $replyTo = null; } else { @@ -114,11 +113,11 @@ private function parseHmsg(string $args): array { $parts = preg_split('/\s+/', trim($args)); - if ($parts === false || count($parts) < 4 || count($parts) > 5) { + if ($parts === false || \count($parts) < 4 || \count($parts) > 5) { throw new ProtocolException("Invalid HMSG line: HMSG {$args}"); } - if (count($parts) === 4) { + if (\count($parts) === 4) { [$subject, $sid, $headerBytes, $totalBytes] = $parts; $replyTo = null; } else { @@ -173,8 +172,8 @@ private function readLine(?float $timeout = null): string private function readExactly(int $bytes): string { - while (strlen($this->buffer) < $bytes) { - $data = $this->transport->read(max(65536, $bytes - strlen($this->buffer))); + while (\strlen($this->buffer) < $bytes) { + $data = $this->transport->read(max(65536, $bytes - \strlen($this->buffer))); if ($data === '') { throw new ProtocolException('Unexpected end of data while reading payload'); } diff --git a/src/Protocol/Writer.php b/src/Protocol/Writer.php index 5386964cdcf..329bdb210e6 100644 --- a/src/Protocol/Writer.php +++ b/src/Protocol/Writer.php @@ -13,7 +13,7 @@ public function connect(array $options): string public function pub(string $subject, string $payload, ?string $replyTo = null): string { - $size = strlen($payload); + $size = \strlen($payload); if ($replyTo !== null) { return "PUB {$subject} {$replyTo} {$size}\r\n{$payload}\r\n"; @@ -24,8 +24,8 @@ public function pub(string $subject, string $payload, ?string $replyTo = null): public function hpub(string $subject, string $headers, string $payload, ?string $replyTo = null): string { - $headerSize = strlen($headers); - $totalSize = $headerSize + strlen($payload); + $headerSize = \strlen($headers); + $totalSize = $headerSize + \strlen($payload); if ($replyTo !== null) { return "HPUB {$subject} {$replyTo} {$headerSize} {$totalSize}\r\n{$headers}{$payload}\r\n"; diff --git a/src/ServerInfo.php b/src/ServerInfo.php index f591e5486ad..bc151b40f72 100644 --- a/src/ServerInfo.php +++ b/src/ServerInfo.php @@ -23,8 +23,7 @@ public function __construct( public readonly bool $jetstream, public readonly ?int $clientId, public readonly ?string $clientIp, - ) { - } + ) {} public static function fromArray(array $data): self { diff --git a/src/Subscription.php b/src/Subscription.php index 135c8d118f4..3b6b29f03e9 100644 --- a/src/Subscription.php +++ b/src/Subscription.php @@ -34,36 +34,25 @@ public function setConnection(Connection $connection): void public function nextMessage(?float $timeout = null): ?Message { - if (!$this->active) { - return null; - } - - // Return from pending queue first - if (!$this->pendingMessages->isEmpty()) { - return $this->pendingMessages->dequeue(); - } + $deadline = $timeout !== null ? microtime(true) + $timeout : null; - // Block and process messages until one arrives for this subscription - if ($this->connection === null) { - return null; - } + while (true) { + // Drain anything already queued before blocking on the socket. + if (!$this->pendingMessages->isEmpty()) { + return $this->pendingMessages->dequeue(); + } - $deadline = $timeout !== null ? microtime(true) + $timeout : null; + if (!$this->active || $this->connection === null) { + return null; + } - while ($this->active) { $remaining = $deadline !== null ? $deadline - microtime(true) : null; if ($remaining !== null && $remaining <= 0) { return null; } $this->connection->processMessage($remaining); - - if (!$this->pendingMessages->isEmpty()) { - return $this->pendingMessages->dequeue(); - } } - - return null; } public function unsubscribe(?int $afterMessages = null): void diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index bf4a13fd7af..44653d35f70 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -124,7 +124,7 @@ public function upgradeTls(array $options): void $merged = array_merge_recursive($context, $contextOptions); foreach ($merged as $wrapper => $opts) { foreach ($opts as $key => $value) { - stream_context_set_option($stream, $wrapper, $key, is_array($value) ? end($value) : $value); + stream_context_set_option($stream, $wrapper, $key, \is_array($value) ? end($value) : $value); } } @@ -170,6 +170,6 @@ private function setTimeout($stream, float $timeout): void private function isTimedOut($stream): bool { $info = stream_get_meta_data($stream); - return $info['timed_out'] ?? false; + return $info['timed_out']; } } diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php index 610e1f583fe..d46cb966c05 100644 --- a/src/Transport/TlsTransport.php +++ b/src/Transport/TlsTransport.php @@ -14,8 +14,7 @@ final class TlsTransport implements Transport public function __construct( private readonly array $tlsOptions = [], - ) { - } + ) {} public function connect(string $host, int $port, float $timeout): void { @@ -72,7 +71,7 @@ public function read(int $maxBytes, ?float $timeout = null): string if ($data === false) { $info = stream_get_meta_data($stream); - if ($info['timed_out'] ?? false) { + if ($info['timed_out']) { throw new TimeoutException('Read timed out'); } throw new ConnectionException('Failed to read from TLS socket'); @@ -99,7 +98,7 @@ public function readLine(?float $timeout = null): string if ($line === false) { $info = stream_get_meta_data($stream); - if ($info['timed_out'] ?? false) { + if ($info['timed_out']) { throw new TimeoutException('Read timed out'); } if (feof($stream)) { diff --git a/tests/Integration/ConnectionTest.php b/tests/E2E/ConnectionTest.php similarity index 97% rename from tests/Integration/ConnectionTest.php rename to tests/E2E/ConnectionTest.php index 1b1132e2940..12d348be2f1 100644 --- a/tests/Integration/ConnectionTest.php +++ b/tests/E2E/ConnectionTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Integration; +namespace Utopia\NATS\Tests\E2E; +use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; use Utopia\NATS\ConnectionOptions; -use PHPUnit\Framework\TestCase; /** * Integration tests require a running NATS server at localhost:4222. diff --git a/tests/Integration/PubSubTest.php b/tests/E2E/PubSubTest.php similarity index 98% rename from tests/Integration/PubSubTest.php rename to tests/E2E/PubSubTest.php index 77e6512738e..5aa2551a908 100644 --- a/tests/Integration/PubSubTest.php +++ b/tests/E2E/PubSubTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Integration; +namespace Utopia\NATS\Tests\E2E; +use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; use Utopia\NATS\Headers; -use PHPUnit\Framework\TestCase; /** * Integration tests require a running NATS server at localhost:4222. diff --git a/tests/Integration/RequestReplyTest.php b/tests/E2E/RequestReplyTest.php similarity index 59% rename from tests/Integration/RequestReplyTest.php rename to tests/E2E/RequestReplyTest.php index a4cdd1d96bf..0a8b543ac5d 100644 --- a/tests/Integration/RequestReplyTest.php +++ b/tests/E2E/RequestReplyTest.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Integration; +namespace Utopia\NATS\Tests\E2E; +use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; +use Utopia\NATS\Exception\NatsException; use Utopia\NATS\Exception\TimeoutException; -use PHPUnit\Framework\TestCase; /** * Integration tests require a running NATS server at localhost:4222. @@ -40,8 +41,26 @@ public function testRequestTimeout(): void { $conn = Connection::connect($this->getServerUrl()); + // Subscribe a silent responder so the server sees interest and does not + // short-circuit with a "no responders" reply — the request must hang + // until it times out. + $conn->subscribe('test.silent', fn($msg) => null); + $this->expectException(TimeoutException::class); - $conn->request('test.no-responder-' . uniqid(), 'hello', 0.5); + $conn->request('test.silent', 'hello', 0.5); + + $conn->close(); + } + + public function testRequestNoResponders(): void + { + $conn = Connection::connect($this->getServerUrl()); + + // With no subscriber on the subject the server replies immediately with + // a 503 "no responders" status rather than letting the request hang. + $this->expectException(NatsException::class); + $this->expectExceptionMessage('No responders for request'); + $conn->request('test.no-responder-' . uniqid(), 'hello', 2.0); $conn->close(); } diff --git a/tests/Unit/HeadersTest.php b/tests/Unit/HeadersTest.php index a6dbb9e4513..55eec374887 100644 --- a/tests/Unit/HeadersTest.php +++ b/tests/Unit/HeadersTest.php @@ -4,8 +4,8 @@ namespace Utopia\NATS\Tests\Unit; -use Utopia\NATS\Headers; use PHPUnit\Framework\TestCase; +use Utopia\NATS\Headers; final class HeadersTest extends TestCase { diff --git a/tests/Unit/InboxTest.php b/tests/Unit/InboxTest.php index 53f02d5737c..cd266d5df94 100644 --- a/tests/Unit/InboxTest.php +++ b/tests/Unit/InboxTest.php @@ -4,8 +4,8 @@ namespace Utopia\NATS\Tests\Unit; -use Utopia\NATS\Inbox; use PHPUnit\Framework\TestCase; +use Utopia\NATS\Inbox; final class InboxTest extends TestCase { @@ -13,7 +13,7 @@ public function testCreateWithDefaultPrefix(): void { $inbox = Inbox::create(); $this->assertStringStartsWith('_INBOX.', $inbox); - $this->assertSame(29, strlen($inbox)); // "_INBOX." (7) + 22 chars + $this->assertSame(29, \strlen($inbox)); // "_INBOX." (7) + 22 chars } public function testCreateWithCustomPrefix(): void @@ -32,7 +32,7 @@ public function testCreateUnique(): void public function testGenerateId(): void { $id = Inbox::generateId(); - $this->assertSame(22, strlen($id)); + $this->assertSame(22, \strlen($id)); $this->assertMatchesRegularExpression('/^[0-9A-Za-z]+$/', $id); } } diff --git a/tests/Unit/Protocol/ParserTest.php b/tests/Unit/Protocol/ParserTest.php index b5f4a130159..1ceafcbed1b 100644 --- a/tests/Unit/Protocol/ParserTest.php +++ b/tests/Unit/Protocol/ParserTest.php @@ -4,17 +4,17 @@ namespace Utopia\NATS\Tests\Unit\Protocol; +use PHPUnit\Framework\TestCase; use Utopia\NATS\Exception\ProtocolException; use Utopia\NATS\Protocol\Parser; use Utopia\NATS\Protocol\ServerOp; use Utopia\NATS\Transport\Transport; -use PHPUnit\Framework\TestCase; final class ParserTest extends TestCase { private function createParser(string $data): Parser { - $transport = new class($data) implements Transport { + $transport = new class ($data) implements Transport { private int $pos = 0; public function __construct(private readonly string $data) {} @@ -23,16 +23,16 @@ public function connect(string $host, int $port, float $timeout): void {} public function write(string $data): int { - return strlen($data); + return \strlen($data); } public function read(int $maxBytes, ?float $timeout = null): string { - if ($this->pos >= strlen($this->data)) { + if ($this->pos >= \strlen($this->data)) { return ''; } $chunk = substr($this->data, $this->pos, $maxBytes); - $this->pos += strlen($chunk); + $this->pos += \strlen($chunk); return $chunk; } @@ -48,7 +48,10 @@ public function readLine(?float $timeout = null): string } public function upgradeTls(array $options): void {} - public function isConnected(): bool { return true; } + public function isConnected(): bool + { + return true; + } public function close(): void {} }; @@ -127,9 +130,9 @@ public function testParseMsgEmpty(): void public function testParseHmsg(): void { $headers = "NATS/1.0\r\nX-Test: value\r\n\r\n"; - $headerLen = strlen($headers); + $headerLen = \strlen($headers); $payload = 'hello'; - $totalLen = $headerLen + strlen($payload); + $totalLen = $headerLen + \strlen($payload); $parser = $this->createParser("HMSG foo.bar 1 {$headerLen} {$totalLen}\r\n{$headers}{$payload}\r\n"); [$op, $data] = $parser->next(); $this->assertSame(ServerOp::HMsg, $op); @@ -141,7 +144,7 @@ public function testParseHmsg(): void public function testParseHmsgWithReply(): void { $headers = "NATS/1.0\r\n\r\n"; - $headerLen = strlen($headers); + $headerLen = \strlen($headers); $totalLen = $headerLen + 3; $parser = $this->createParser("HMSG foo 1 reply {$headerLen} {$totalLen}\r\n{$headers}bar\r\n"); [$op, $data] = $parser->next(); diff --git a/tests/Unit/Protocol/WriterTest.php b/tests/Unit/Protocol/WriterTest.php index a63b1914f20..8e9dc354bd9 100644 --- a/tests/Unit/Protocol/WriterTest.php +++ b/tests/Unit/Protocol/WriterTest.php @@ -4,8 +4,8 @@ namespace Utopia\NATS\Tests\Unit\Protocol; -use Utopia\NATS\Protocol\Writer; use PHPUnit\Framework\TestCase; +use Utopia\NATS\Protocol\Writer; final class WriterTest extends TestCase { @@ -44,7 +44,7 @@ public function testHpub(): void { $headers = "NATS/1.0\r\nX-Key: value\r\n\r\n"; $result = $this->writer->hpub('foo', $headers, 'hello'); - $headerLen = strlen($headers); + $headerLen = \strlen($headers); $totalLen = $headerLen + 5; $this->assertSame("HPUB foo {$headerLen} {$totalLen}\r\n{$headers}hello\r\n", $result); } @@ -53,7 +53,7 @@ public function testHpubWithReply(): void { $headers = "NATS/1.0\r\n\r\n"; $result = $this->writer->hpub('foo', $headers, 'data', 'reply'); - $headerLen = strlen($headers); + $headerLen = \strlen($headers); $totalLen = $headerLen + 4; $this->assertSame("HPUB foo reply {$headerLen} {$totalLen}\r\n{$headers}data\r\n", $result); } From b0630393f0477f85e0f9f01480224c60c5325730 Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:48:18 +0100 Subject: [PATCH 348/470] Merge pull request #40 from utopia-php/feat/rector-hoist build: hoist baseline rector + phpstan configs to the root --- phpstan.neon | 9 +++----- src/Auth/CredentialsAuth.php | 6 +++--- src/Auth/NKeyAuth.php | 22 ++++++++------------ src/Connection.php | 33 +++++++++++++++--------------- src/Headers.php | 6 ++---- src/JetStream/Consumer.php | 6 +++--- src/JetStream/JetStream.php | 10 +++------ src/JetStream/JetStreamMessage.php | 2 +- src/JetStream/MsgMetadata.php | 2 +- src/JetStream/StreamConfig.php | 2 +- src/KeyValue/KeyValue.php | 12 +++++------ src/KeyValue/KeyValueConfig.php | 4 ++-- src/Subscription.php | 16 ++++++--------- src/Transport/TcpTransport.php | 14 +++---------- src/Transport/TlsTransport.php | 2 +- tests/E2E/PubSubTest.php | 12 +++++------ tests/E2E/RequestReplyTest.php | 2 +- 17 files changed, 66 insertions(+), 94 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index ef0ae2f1823..f3da750a966 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,5 @@ +includes: + - ../../phpstan.neon + parameters: - level: 5 - paths: - - src - - tests - excludePaths: - - vendor treatPhpDocTypesAsCertain: false diff --git a/src/Auth/CredentialsAuth.php b/src/Auth/CredentialsAuth.php index 9e66daf39ed..04d3e7df96b 100644 --- a/src/Auth/CredentialsAuth.php +++ b/src/Auth/CredentialsAuth.php @@ -22,8 +22,8 @@ public function __construct(string $credentialsFile) throw new AuthenticationException("Failed to read credentials file: {$credentialsFile}"); } - $jwt = self::extractBetween($contents, '-----BEGIN NATS USER JWT-----', '------END NATS USER JWT------'); - $seed = self::extractBetween($contents, '-----BEGIN USER NKEY SEED-----', '------END USER NKEY SEED------'); + $jwt = $this->extractBetween($contents, '-----BEGIN NATS USER JWT-----', '------END NATS USER JWT------'); + $seed = $this->extractBetween($contents, '-----BEGIN USER NKEY SEED-----', '------END USER NKEY SEED------'); if ($jwt === null) { throw new AuthenticationException('No JWT found in credentials file'); @@ -49,7 +49,7 @@ public function authenticate(?string $nonce = null): array ]; } - private static function extractBetween(string $content, string $begin, string $end): ?string + private function extractBetween(string $content, string $begin, string $end): ?string { $startPos = strpos($content, $begin); if ($startPos === false) { diff --git a/src/Auth/NKeyAuth.php b/src/Auth/NKeyAuth.php index 13b56718f0e..916e328e7c3 100644 --- a/src/Auth/NKeyAuth.php +++ b/src/Auth/NKeyAuth.php @@ -8,19 +8,13 @@ final class NKeyAuth implements Authenticator { - private readonly string $publicKey; - private readonly string $seed; - public function __construct( - string $nkey, - string $nkeySeed, + private readonly string $publicKey, + private readonly string $seed, ) { if (!\function_exists('sodium_crypto_sign_detached')) { throw new AuthenticationException('NKey authentication requires the sodium PHP extension'); } - - $this->publicKey = $nkey; - $this->seed = $nkeySeed; } public function authenticate(?string $nonce = null): array @@ -29,20 +23,20 @@ public function authenticate(?string $nonce = null): array throw new AuthenticationException('NKey authentication requires a server nonce'); } - $rawSeed = self::decodeSeed($this->seed); + $rawSeed = $this->decodeSeed($this->seed); $keyPair = sodium_crypto_sign_seed_keypair($rawSeed); $secretKey = sodium_crypto_sign_secretkey($keyPair); $signature = sodium_crypto_sign_detached($nonce, $secretKey); return [ 'nkey' => $this->publicKey, - 'sig' => self::base32Encode($signature), + 'sig' => $this->base32Encode($signature), ]; } - private static function decodeSeed(string $seed): string + private function decodeSeed(string $seed): string { - $decoded = self::base32Decode($seed); + $decoded = $this->base32Decode($seed); if (\strlen($decoded) < 4) { throw new AuthenticationException('Invalid NKey seed'); } @@ -51,7 +45,7 @@ private static function decodeSeed(string $seed): string return substr($decoded, 2, -2); } - private static function base32Decode(string $input): string + private function base32Decode(string $input): string { $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $input = strtoupper(rtrim($input, '=')); @@ -76,7 +70,7 @@ private static function base32Decode(string $input): string return $output; } - private static function base32Encode(string $input): string + private function base32Encode(string $input): string { $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $output = ''; diff --git a/src/Connection.php b/src/Connection.php index 941ed9b1d2c..463243b4ee1 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -38,7 +38,7 @@ final class Connection private Transport $transport; private Parser $parser; - private Writer $writer; + private readonly Writer $writer; private Authenticator $auth; private ServerInfo $serverInfo; private ConnectionOptions $options; @@ -78,7 +78,7 @@ public static function connect( ): self { if ($urlOrOptions instanceof ConnectionOptions) { $options = $urlOrOptions; - } elseif ($options === null) { + } elseif (!$options instanceof \Utopia\NATS\ConnectionOptions) { $options = new ConnectionOptions(servers: $urlOrOptions); } @@ -101,7 +101,7 @@ public function publish(string $subject, string $data = '', ?string $replyTo = n ); } - if ($headers !== null && !empty($headers->all())) { + if ($headers instanceof \Utopia\NATS\Headers && $headers->all() !== []) { $headerWire = $headers->toWire(); $cmd = $this->writer->hpub($subject, $headerWire, $data, $replyTo); } else { @@ -226,7 +226,7 @@ public function wait(int $count = 0, ?float $timeout = null): void } $msg = $this->processMessage($remaining); - if ($msg !== null) { + if ($msg instanceof \Utopia\NATS\Message) { $processed++; } } @@ -276,7 +276,7 @@ public function drain(?float $timeout = null): void // Process remaining messages $deadline = microtime(true) + $timeout; - while (!empty($this->subscriptions)) { + while ($this->subscriptions !== []) { $remaining = $deadline - microtime(true); if ($remaining <= 0) { break; @@ -311,7 +311,7 @@ public function close(): void $this->transport->close(); } - if ($previousStatus === self::STATUS_CONNECTED && $this->options->onClose !== null) { + if ($previousStatus === self::STATUS_CONNECTED && $this->options->onClose instanceof \Closure) { ($this->options->onClose)(); } } @@ -348,7 +348,7 @@ private function doConnect(): void $this->status = self::STATUS_CONNECTING; $lastError = null; - foreach ($this->serverPool as $index => $url) { + foreach ($this->serverPool as $url) { try { $this->connectToServer($url); $this->status = self::STATUS_CONNECTED; @@ -462,9 +462,8 @@ private function buildConnectPayload(): array } $authFields = $this->auth->authenticate($this->serverInfo->nonce); - $payload = array_merge($payload, $authFields); - return $payload; + return array_merge($payload, $authFields); } private function dispatchOp(ServerOp $op, mixed $data): ?Message @@ -495,7 +494,7 @@ private function handleMessage(array $data): Message ); // Check if this is a reply to a pending request (mux inbox) - if ($this->inboxSub !== null && $data['sid'] === $this->inboxSub->sid) { + if ($this->inboxSub instanceof \Utopia\NATS\Subscription && $data['sid'] === $this->inboxSub->sid) { $token = $this->extractInboxToken($data['subject']); if ($token !== null && isset($this->pendingRequests[$token])) { $this->pendingRequests[$token] = ['message' => $msg, 'resolved' => true]; @@ -533,7 +532,7 @@ private function handleError(mixed $data): never { $message = \is_string($data) ? $data : 'Unknown server error'; - if ($this->options->onError !== null) { + if ($this->options->onError instanceof \Closure) { ($this->options->onError)(new NatsException($message)); } @@ -595,7 +594,7 @@ private function attemptReconnect(): void $this->status = self::STATUS_RECONNECTING; - if ($this->options->onDisconnect !== null) { + if ($this->options->onDisconnect instanceof \Closure) { ($this->options->onDisconnect)(); } @@ -610,7 +609,7 @@ private function attemptReconnect(): void usleep((int) ($wait * 1_000_000)); } - foreach ($this->serverPool as $index => $url) { + foreach ($this->serverPool as $url) { try { $this->connectToServer($url); $this->status = self::STATUS_CONNECTED; @@ -629,7 +628,7 @@ private function attemptReconnect(): void } $this->pendingBuffer = []; - if ($this->options->onReconnect !== null) { + if ($this->options->onReconnect instanceof \Closure) { ($this->options->onReconnect)(); } @@ -672,7 +671,7 @@ private function send(string $data): void private function ensureInboxSub(): void { - if ($this->inboxSub !== null) { + if ($this->inboxSub instanceof \Utopia\NATS\Subscription) { return; } @@ -729,7 +728,7 @@ private function resolveAuthenticator(ConnectionOptions $options): Authenticator */ private function buildServerPool(ConnectionOptions $options): array { - $servers = array_map(fn(string $url) => $this->normalizeUrl($url), $options->servers); + $servers = array_map($this->normalizeUrl(...), $options->servers); if (!$options->noRandomize && \count($servers) > 1) { shuffle($servers); @@ -741,7 +740,7 @@ private function buildServerPool(ConnectionOptions $options): array private function normalizeUrl(string $url): string { if (!preg_match('#^(nats|tls|ws|wss)://#', $url)) { - $url = 'nats://' . $url; + return 'nats://' . $url; } return $url; } diff --git a/src/Headers.php b/src/Headers.php index 2619430f593..aacbd99593f 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -87,9 +87,7 @@ public function toWire(): string } } - $result .= "\r\n"; - - return $result; + return $result . "\r\n"; } public static function fromWire(string $raw): self @@ -97,7 +95,7 @@ public static function fromWire(string $raw): self $headers = new self(); $lines = explode("\r\n", $raw); - if (empty($lines)) { + if ($lines === []) { throw new ProtocolException('Empty header block'); } diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index e56b170f270..07d413b09c4 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -44,14 +44,14 @@ public function fetch(int $batch = 1, ?float $timeout = null): MessageBatch } $msg = $sub->nextMessage($remaining); - if ($msg === null) { + if (!$msg instanceof \Utopia\NATS\Message) { break; } // Check for status messages (408 = Request Timeout, 404 = No Messages, 409 = Leadership Change) - if ($msg->headers !== null) { + if ($msg->headers instanceof \Utopia\NATS\Headers) { $status = $msg->headers->getStatus(); - if ($status === '408' || $status === '404' || $status === '409') { + if (\in_array($status, ['408', '404', '409'], true)) { break; } } diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 59629a2b441..0e62eb8e2bd 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -47,7 +47,7 @@ public function createOrUpdateStream(StreamConfig $config): Stream try { return $this->updateStream($config); } catch (JetStreamException $e) { - if ($e->apiError !== null && $e->apiError->code === 404) { + if ($e->apiError instanceof \Utopia\NATS\JetStream\ApiError && $e->apiError->code === 404) { return $this->createStream($config); } throw $e; @@ -85,7 +85,7 @@ public function listStreams(?string $subject = null): array $payload = $subject !== null ? ['subject' => $subject] : null; $data = $this->apiRequest('STREAM.LIST', $payload); return array_map( - fn(array $s) => StreamInfo::fromArray($s), + StreamInfo::fromArray(...), $data['streams'] ?? [], ); } @@ -102,11 +102,7 @@ public function createConsumer(string $stream, ConsumerConfig $config): Consumer { $consumerName = $config->name ?? $config->durableName; - if ($consumerName !== null) { - $subject = "CONSUMER.CREATE.{$stream}.{$consumerName}"; - } else { - $subject = "CONSUMER.CREATE.{$stream}"; - } + $subject = $consumerName !== null ? "CONSUMER.CREATE.{$stream}.{$consumerName}" : "CONSUMER.CREATE.{$stream}"; $payload = [ 'stream_name' => $stream, diff --git a/src/JetStream/JetStreamMessage.php b/src/JetStream/JetStreamMessage.php index 118652cb44a..6baff6d0e35 100644 --- a/src/JetStream/JetStreamMessage.php +++ b/src/JetStream/JetStreamMessage.php @@ -48,7 +48,7 @@ public function term(?string $reason = null): void public function metadata(): MsgMetadata { - if ($this->metadata === null) { + if (!$this->metadata instanceof \Utopia\NATS\JetStream\MsgMetadata) { if ($this->message->replyTo === null) { throw new \RuntimeException('Message has no reply subject for metadata parsing'); } diff --git a/src/JetStream/MsgMetadata.php b/src/JetStream/MsgMetadata.php index 1f1717d951a..5d13bd99e94 100644 --- a/src/JetStream/MsgMetadata.php +++ b/src/JetStream/MsgMetadata.php @@ -42,7 +42,6 @@ public static function fromReplySubject(string $reply): self // Domain format has extra parts if (\count($parts) >= 11) { return new self( - domain: $parts[2], stream: $parts[4], consumer: $parts[5], numDelivered: (int) $parts[6], @@ -50,6 +49,7 @@ public static function fromReplySubject(string $reply): self consumerSequence: (int) $parts[8], timestamp: $parts[9], numPending: (int) $parts[10], + domain: $parts[2], ); } } diff --git a/src/JetStream/StreamConfig.php b/src/JetStream/StreamConfig.php index 24aa0be8d1b..8c54a4ef262 100644 --- a/src/JetStream/StreamConfig.php +++ b/src/JetStream/StreamConfig.php @@ -56,7 +56,7 @@ public function toArray(): array 'allow_rollup_hdrs' => $this->allowRollup, ]; - if (!empty($this->subjects)) { + if ($this->subjects !== []) { $data['subjects'] = $this->subjects; } if ($this->description !== null) { diff --git a/src/KeyValue/KeyValue.php b/src/KeyValue/KeyValue.php index 7973ee86aa0..3ec8708fe8c 100644 --- a/src/KeyValue/KeyValue.php +++ b/src/KeyValue/KeyValue.php @@ -32,7 +32,7 @@ public function get(string $key): KeyValueEntry } // Check for delete/purge markers - if ($msg->headers !== null) { + if ($msg->headers instanceof \Utopia\NATS\Headers) { $op = $msg->headers->get('KV-Operation'); if ($op === 'DEL' || $op === 'PURGE') { throw new KeyValueException("Key not found: {$key}"); @@ -41,7 +41,7 @@ public function get(string $key): KeyValueEntry $revision = 0; $created = null; - if ($msg->headers !== null) { + if ($msg->headers instanceof \Utopia\NATS\Headers) { $seqStr = $msg->headers->get('Nats-Sequence'); if ($seqStr !== null) { $revision = (int) $seqStr; @@ -86,7 +86,7 @@ public function create(string $key, string $value): int try { $ack = $this->js->publish($subject, $value, $headers); } catch (\Throwable $e) { - throw new KeyValueException("Key already exists: {$key}", previous: $e); + throw new KeyValueException("Key already exists: {$key}", $e->getCode(), previous: $e); } return $ack->sequence; @@ -108,7 +108,7 @@ public function update(string $key, string $value, int $revision): int expectedLastSubjectSeq: $revision, ); } catch (\Throwable $e) { - throw new KeyValueException("Wrong last revision for key: {$key}", previous: $e); + throw new KeyValueException("Wrong last revision for key: {$key}", $e->getCode(), previous: $e); } return $ack->sequence; @@ -156,8 +156,8 @@ public function keys(): array $prefix = "\$KV.{$this->bucket}."; $subjects = $data['state']['subjects'] ?? []; foreach ($subjects as $subj => $count) { - if (str_starts_with($subj, $prefix)) { - $keys[] = substr($subj, \strlen($prefix)); + if (str_starts_with((string) $subj, $prefix)) { + $keys[] = substr((string) $subj, \strlen($prefix)); } } diff --git a/src/KeyValue/KeyValueConfig.php b/src/KeyValue/KeyValueConfig.php index 242937628a3..7418853b774 100644 --- a/src/KeyValue/KeyValueConfig.php +++ b/src/KeyValue/KeyValueConfig.php @@ -32,16 +32,16 @@ public function toStreamConfig(): StreamConfig subjects: ["\$KV.{$this->bucket}.>"], description: $this->description, retention: RetentionPolicy::Limits, - maxMsgsPerSubject: $this->history, maxBytes: $this->maxBytes, + maxMsgsPerSubject: $this->history, maxMsgSize: $this->maxValueSize > 0 ? $this->maxValueSize : null, maxAge: $this->ttl, storage: $this->storage, replicas: $this->replicas, discard: DiscardPolicy::New, - allowRollup: true, allowDirect: true, mirrorDirect: true, + allowRollup: true, ); } } diff --git a/src/Subscription.php b/src/Subscription.php index 3b6b29f03e9..7a1a1c6039d 100644 --- a/src/Subscription.php +++ b/src/Subscription.php @@ -6,14 +6,11 @@ final class Subscription { - private \SplQueue $pendingMessages; + private readonly \SplQueue $pendingMessages; private bool $active = true; private ?int $maxMessages = null; private int $received = 0; - /** @var \Closure|null */ - private ?\Closure $callback; - /** @var Connection|null Back-reference for sync operations */ private ?Connection $connection = null; @@ -21,9 +18,8 @@ public function __construct( public readonly string $sid, public readonly string $subject, public readonly ?string $queue = null, - ?\Closure $callback = null, + private readonly ?\Closure $callback = null, ) { - $this->callback = $callback; $this->pendingMessages = new \SplQueue(); } @@ -42,7 +38,7 @@ public function nextMessage(?float $timeout = null): ?Message return $this->pendingMessages->dequeue(); } - if (!$this->active || $this->connection === null) { + if (!$this->active || !$this->connection instanceof \Utopia\NATS\Connection) { return null; } @@ -57,7 +53,7 @@ public function nextMessage(?float $timeout = null): ?Message public function unsubscribe(?int $afterMessages = null): void { - if ($this->connection !== null) { + if ($this->connection instanceof \Utopia\NATS\Connection) { $this->connection->unsubscribe($this, $afterMessages); } } @@ -71,7 +67,7 @@ public function deliver(Message $msg): void { $this->received++; - if ($this->callback !== null) { + if ($this->callback instanceof \Closure) { ($this->callback)($msg); } else { $this->pendingMessages->enqueue($msg); @@ -102,6 +98,6 @@ public function getReceived(): int public function hasCallback(): bool { - return $this->callback !== null; + return $this->callback instanceof \Closure; } } diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index 44653d35f70..8682e81ac3c 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -10,7 +10,7 @@ final class TcpTransport implements Transport { /** @var resource|null */ - private $stream = null; + private $stream; public function connect(string $host, int $port, float $timeout): void { @@ -109,16 +109,8 @@ public function upgradeTls(array $options): void if (isset($options['local_pk'])) { $contextOptions['ssl']['local_pk'] = $options['local_pk']; } - if (isset($options['verify_peer'])) { - $contextOptions['ssl']['verify_peer'] = $options['verify_peer']; - } else { - $contextOptions['ssl']['verify_peer'] = true; - } - if (isset($options['verify_peer_name'])) { - $contextOptions['ssl']['verify_peer_name'] = $options['verify_peer_name']; - } else { - $contextOptions['ssl']['verify_peer_name'] = true; - } + $contextOptions['ssl']['verify_peer'] = $options['verify_peer'] ?? true; + $contextOptions['ssl']['verify_peer_name'] = $options['verify_peer_name'] ?? true; $context = stream_context_get_options($stream); $merged = array_merge_recursive($context, $contextOptions); diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php index d46cb966c05..6e0603b5d59 100644 --- a/src/Transport/TlsTransport.php +++ b/src/Transport/TlsTransport.php @@ -10,7 +10,7 @@ final class TlsTransport implements Transport { /** @var resource|null */ - private $stream = null; + private $stream; public function __construct( private readonly array $tlsOptions = [], diff --git a/tests/E2E/PubSubTest.php b/tests/E2E/PubSubTest.php index 5aa2551a908..d2fa5105a6f 100644 --- a/tests/E2E/PubSubTest.php +++ b/tests/E2E/PubSubTest.php @@ -23,7 +23,7 @@ public function testPubSub(): void $conn = Connection::connect($this->getServerUrl()); $received = null; - $sub = $conn->subscribe('test.pubsub', function ($msg) use (&$received) { + $sub = $conn->subscribe('test.pubsub', function ($msg) use (&$received): void { $received = $msg; }); @@ -47,7 +47,7 @@ public function testSyncSubscribe(): void $conn->publish('test.sync', 'sync-message'); $msg = $sub->nextMessage(1.0); - $this->assertNotNull($msg); + $this->assertInstanceOf(\Utopia\NATS\Message::class, $msg); $this->assertSame('sync-message', $msg->data); $sub->unsubscribe(); @@ -59,7 +59,7 @@ public function testWildcard(): void $conn = Connection::connect($this->getServerUrl()); $messages = []; - $sub = $conn->subscribe('test.wild.*', function ($msg) use (&$messages) { + $sub = $conn->subscribe('test.wild.*', function ($msg) use (&$messages): void { $messages[] = $msg; }); @@ -81,10 +81,10 @@ public function testQueueSubscribe(): void $count1 = 0; $count2 = 0; - $sub1 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count1) { + $sub1 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count1): void { $count1++; }); - $sub2 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count2) { + $sub2 = $conn->queueSubscribe('test.queue', 'workers', function () use (&$count2): void { $count2++; }); @@ -108,7 +108,7 @@ public function testPubSubWithHeaders(): void $conn = Connection::connect($this->getServerUrl()); $received = null; - $sub = $conn->subscribe('test.headers', function ($msg) use (&$received) { + $sub = $conn->subscribe('test.headers', function ($msg) use (&$received): void { $received = $msg; }); diff --git a/tests/E2E/RequestReplyTest.php b/tests/E2E/RequestReplyTest.php index 0a8b543ac5d..14488fd86e2 100644 --- a/tests/E2E/RequestReplyTest.php +++ b/tests/E2E/RequestReplyTest.php @@ -24,7 +24,7 @@ public function testRequestReply(): void $conn = Connection::connect($this->getServerUrl()); // Set up responder - $sub = $conn->subscribe('test.echo', function ($msg) use ($conn) { + $sub = $conn->subscribe('test.echo', function ($msg) use ($conn): void { if ($msg->replyTo !== null) { $conn->publish($msg->replyTo, 'echo: ' . $msg->data); } From 15dad9d542f8f194ee4b7d3a61eb7027d49acd61 Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:10:46 +0100 Subject: [PATCH 349/470] Merge pull request #48 from utopia-php/refactor/mirror-workflow Reuse mirror redirect workflow --- .github/workflows/mirror.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index f5a4c104156..469a093888b 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -3,18 +3,15 @@ name: Mirror on: pull_request_target: types: [opened] + issues: + types: [opened] permissions: + issues: write pull-requests: write jobs: redirect: - runs-on: ubuntu-latest - steps: - - name: Redirect pull request to the monorepo - env: - GH_TOKEN: ${{ github.token }} - PR: ${{ github.event.pull_request.html_url }} - run: | - gh pr comment "$PR" --body "Thanks for contributing! This repository is a read-only mirror — development for this library happens in [\`packages/nats\`](https://github.com/utopia-php/monorepo/tree/main/packages/nats) in the [utopia-php monorepo](https://github.com/utopia-php/monorepo). Please open this pull request there instead." - gh pr close "$PR" + uses: utopia-php/monorepo/.github/workflows/mirror-redirect.yml@main + with: + package: nats From efd0553ecdd72fdccced634f65346d0e5e8d56f0 Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:18:49 +0100 Subject: [PATCH 350/470] Merge pull request #69 from utopia-php/docs/vale-linter docs: enforce documentation style with Vale --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f4e03d3ef9f..b12d1ed69cb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A modern PHP client for [NATS](https://nats.io) messaging system with JetStream composer require utopia-php/nats ``` -## Quick Start +## Quick start ```php use Utopia\NATS\Connection; @@ -105,7 +105,7 @@ $sub->unsubscribe(); $conn->unsubscribe($sub, maxMessages: 10); ``` -### Request-Reply +### Request-reply ```php // Responder @@ -119,7 +119,7 @@ $response = $conn->request('math.double', '21', timeout: 2.0); echo $response->data; // "42" ``` -### Connection Management +### Connection management ```php $conn->flush(); // Ensure all messages are sent @@ -162,7 +162,7 @@ $names = $js->getStreamNames(); $stream->delete(); ``` -### Publishing with Acknowledgment +### Publishing with acknowledgment ```php $ack = $js->publish('orders.new', '{"id": 1}'); @@ -211,7 +211,7 @@ echo "Stream seq: {$meta->streamSequence}\n"; echo "Deliveries: {$meta->numDelivered}\n"; ``` -## Key-Value Store +## Key-value store ```php use Utopia\NATS\KeyValue\KeyValueConfig; @@ -304,7 +304,7 @@ $conn = Connection::connect(new ConnectionOptions( )); ``` -## Event Callbacks +## Event callbacks ```php $conn = Connection::connect(new ConnectionOptions( From f4f03209ed5d0b1456c136c941f7da8fa1d27691 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:03:05 +0100 Subject: [PATCH 351/470] chore(deps)!: upgrade to utopia-php/pools 2.x Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 2 +- tests/Abuse/RedisPoolClusterTest.php | 2 +- tests/Abuse/RedisPoolTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index aa29144739e..a3ee19fb3a5 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-curl": "*", "ext-redis": "*", "utopia-php/database": "^6.0.0", - "utopia-php/pools": "1.*", + "utopia-php/pools": "2.*", "appwrite/appwrite": "^26.0" }, "require-dev": { diff --git a/tests/Abuse/RedisPoolClusterTest.php b/tests/Abuse/RedisPoolClusterTest.php index dbe7eaea3e3..c41f1639963 100644 --- a/tests/Abuse/RedisPoolClusterTest.php +++ b/tests/Abuse/RedisPoolClusterTest.php @@ -27,7 +27,7 @@ public static function setUpBeforeClass(): void 'redis-cluster-2:6379', 'redis-cluster-3:6379', ]); - }); + }, timeout: 0.0); } public function getAdapter(string $key, int $limit, int $seconds): TimeLimit diff --git a/tests/Abuse/RedisPoolTest.php b/tests/Abuse/RedisPoolTest.php index c930fc173d0..9ac98aa083d 100644 --- a/tests/Abuse/RedisPoolTest.php +++ b/tests/Abuse/RedisPoolTest.php @@ -25,7 +25,7 @@ public static function setUpBeforeClass(): void $redis->connect('redis', 6379); return $redis; - }); + }, timeout: 0.0); } public function getAdapter(string $key, int $limit, int $seconds): TimeLimit From 03cc86ee274b2523430ffb56f0ab60aed8c38e42 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:34:58 +0100 Subject: [PATCH 352/470] chore(deps): bump utopia-php/database to 7.x and refresh lock Co-Authored-By: Claude Opus 5 (1M context) --- composer.json | 2 +- composer.lock | 93 ++++++++++++++++++++++++++------------------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index a3ee19fb3a5..0138037271c 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "ext-pdo": "*", "ext-curl": "*", "ext-redis": "*", - "utopia-php/database": "^6.0.0", + "utopia-php/database": "^7.0.0", "utopia-php/pools": "2.*", "appwrite/appwrite": "^26.0" }, diff --git a/composer.lock b/composer.lock index 1c988fbb58c..5f2aab6fd5e 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": "bb890a101e6cacd0317970c692b16083", + "content-hash": "4aff8f896ffc6d7981242fdf9ce459cc", "packages": [ { "name": "appwrite/appwrite", @@ -2075,16 +2075,16 @@ }, { "name": "utopia-php/cache", - "version": "3.0.2", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "086687d7ae23dd1dae67b943161e8cef143539e1" + "reference": "2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/086687d7ae23dd1dae67b943161e8cef143539e1", - "reference": "086687d7ae23dd1dae67b943161e8cef143539e1", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e", + "reference": "2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e", "shasum": "" }, "require": { @@ -2093,7 +2093,7 @@ "ext-redis": "*", "php": ">=8.3", "utopia-php/circuit-breaker": "0.3.*", - "utopia-php/pools": "1.*", + "utopia-php/pools": "2.*", "utopia-php/telemetry": "*" }, "require-dev": { @@ -2123,9 +2123,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/3.0.2" + "source": "https://github.com/utopia-php/cache/tree/4.0.0" }, - "time": "2026-05-19T22:38:16+00:00" + "time": "2026-07-31T13:04:45+00:00" }, { "name": "utopia-php/circuit-breaker", @@ -2239,16 +2239,16 @@ }, { "name": "utopia-php/database", - "version": "6.0.0", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "fff9f0effbd40359ff925741ff9424856d8b4fde" + "reference": "40185b100f92c402a189d6f4f0962c63bbe5726b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/fff9f0effbd40359ff925741ff9424856d8b4fde", - "reference": "fff9f0effbd40359ff925741ff9424856d8b4fde", + "url": "https://api.github.com/repos/utopia-php/database/zipball/40185b100f92c402a189d6f4f0962c63bbe5726b", + "reference": "40185b100f92c402a189d6f4f0962c63bbe5726b", "shasum": "" }, "require": { @@ -2256,12 +2256,12 @@ "ext-mongodb": "*", "ext-pdo": "*", "ext-redis": "*", - "php": ">=8.4", - "utopia-php/cache": "^3.0", + "php": ">=8.5", + "utopia-php/cache": "^4.0.0", "utopia-php/console": "0.1.*", "utopia-php/mongo": "1.*", - "utopia-php/pools": "1.*", - "utopia-php/validators": "0.2.*" + "utopia-php/pools": "2.*", + "utopia-php/validators": "0.3.*" }, "require-dev": { "fakerphp/faker": "1.23.*", @@ -2293,22 +2293,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/6.0.0" + "source": "https://github.com/utopia-php/database/tree/7.0.0" }, - "time": "2026-06-18T10:37:40+00:00" + "time": "2026-07-31T13:33:10+00:00" }, { "name": "utopia-php/mongo", - "version": "1.2.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/mongo.git", - "reference": "0b0aac1cd2772bcafed2d4540d74c15cac9a2d44" + "reference": "78818fd295f2829aaad5b74c9094e8c6f603550d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/0b0aac1cd2772bcafed2d4540d74c15cac9a2d44", - "reference": "0b0aac1cd2772bcafed2d4540d74c15cac9a2d44", + "url": "https://api.github.com/repos/utopia-php/mongo/zipball/78818fd295f2829aaad5b74c9094e8c6f603550d", + "reference": "78818fd295f2829aaad5b74c9094e8c6f603550d", "shasum": "" }, "require": { @@ -2354,22 +2354,22 @@ ], "support": { "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/1.2.0" + "source": "https://github.com/utopia-php/mongo/tree/1.4.0" }, - "time": "2026-06-07T13:02:18+00:00" + "time": "2026-07-30T05:24:58+00:00" }, { "name": "utopia-php/pools", - "version": "1.0.5", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b" + "reference": "48905dac1b8f8050c2e07bdda32a018ca33982fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b", - "reference": "e66e498d59cf3ac48c1f4c70be46f7eb6df7a63b", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/48905dac1b8f8050c2e07bdda32a018ca33982fc", + "reference": "48905dac1b8f8050c2e07bdda32a018ca33982fc", "shasum": "" }, "require": { @@ -2380,7 +2380,10 @@ "swoole/ide-helper": "6.*" }, "suggest": { - "ext-swoole": "Required to use the Swoole pool adapter" + "ext-mongodb": "Needed to support MongoDB database pools", + "ext-pdo": "Needed to support MariaDB, MySQL or SQLite database pools", + "ext-redis": "Needed to support Redis cache pools", + "ext-swoole": "Needed to support Swoole based pool adapter" }, "type": "library", "autoload": { @@ -2407,26 +2410,25 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/1.0.5" + "source": "https://github.com/utopia-php/pools/tree/2.0.1" }, - "time": "2026-06-10T15:14:31+00:00" + "time": "2026-07-31T12:19:18+00:00" }, { "name": "utopia-php/telemetry", - "version": "0.4.1", + "version": "0.4.5", "source": { "type": "git", "url": "https://github.com/utopia-php/telemetry.git", - "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e" + "reference": "139943bffcd4f6dd8fb9ed247f946a1d151b006a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/028f3a85bb987b1e9357fb1632aa40f020d1b86e", - "reference": "028f3a85bb987b1e9357fb1632aa40f020d1b86e", + "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/139943bffcd4f6dd8fb9ed247f946a1d151b006a", + "reference": "139943bffcd4f6dd8fb9ed247f946a1d151b006a", "shasum": "" }, "require": { - "ext-opentelemetry": "*", "ext-protobuf": "*", "nyholm/psr7": "1.*", "open-telemetry/exporter-otlp": "1.*", @@ -2435,7 +2437,6 @@ "symfony/http-client": "7.*" }, "require-dev": { - "phpbench/phpbench": "1.*", "swoole/ide-helper": "6.*" }, "suggest": { @@ -2460,26 +2461,26 @@ ], "support": { "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.4.1" + "source": "https://github.com/utopia-php/telemetry/tree/0.4.5" }, - "time": "2026-06-10T15:48:26+00:00" + "time": "2026-07-08T11:07:25+00:00" }, { "name": "utopia-php/validators", - "version": "0.2.6", + "version": "0.3.1", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b" + "reference": "d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b", - "reference": "f1ac4f08d6876daf117a1d9af53a4c7b752c2c0b", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba", + "reference": "d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba", "shasum": "" }, "require": { - "php": ">=8.0" + "php": ">=8.5" }, "type": "library", "autoload": { @@ -2500,9 +2501,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.2.6" + "source": "https://github.com/utopia-php/validators/tree/0.3.1" }, - "time": "2026-06-10T14:45:13+00:00" + "time": "2026-07-14T11:55:48+00:00" } ], "packages-dev": [ From 1c928b382cc46af81fa58305efad57e904bc5f32 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 31 Jul 2026 15:46:22 +0100 Subject: [PATCH 353/470] chore: drop unused PHP 8.2 and 8.3 test images Co-Authored-By: Claude Opus 5 (1M context) --- Dockerfile.php-8.2 | 27 --------------------------- Dockerfile.php-8.3 | 27 --------------------------- 2 files changed, 54 deletions(-) delete mode 100644 Dockerfile.php-8.2 delete mode 100644 Dockerfile.php-8.3 diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 deleted file mode 100644 index 24e4214eda9..00000000000 --- a/Dockerfile.php-8.2 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY composer.lock /src/ -COPY composer.json /src/ - -RUN composer install --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.2-1.0.0 as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./phpbench.json /code/phpbench.json - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 deleted file mode 100644 index 175276f3925..00000000000 --- a/Dockerfile.php-8.3 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY composer.lock /src/ -COPY composer.json /src/ - -RUN composer install --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.3-1.0.0 as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./phpbench.json /code/phpbench.json - -CMD [ "tail", "-f", "/dev/null" ] From 43c69796f1ad3d8a20ed403dab9d04c4b2d855d0 Mon Sep 17 00:00:00 2001 From: Claudear <262350598+claudear@users.noreply.github.com> Date: Tue, 11 Aug 2026 06:33:35 +0000 Subject: [PATCH 354/470] feat(appwrite): create the abuse table schema inline Appwrite now builds columns and indexes sent inline with create table from the same mapping the dedicated per-column endpoints use, so the workaround of create table + one request per column is no longer needed (appwrite/appwrite#13174). Setup now sends the whole schema with the table in a single request. Inline columns and indexes come back available, so the two polling loops are gone from the cold path. A table left over from a setup that did not run to completion still falls back to the per-column endpoints, since inline definitions only apply while the table is being created; both paths now read the schema from one list instead of restating it. Also bumps appwrite/appwrite to 27.1, and fixes the readiness poll: list responses carry raw payloads rather than hydrated models, so reading status as a property always yielded '' and the poll could only ever time out. Co-Authored-By: Claude Opus 5 --- composer.json | 2 +- composer.lock | 76 +++++++++- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 142 +++++++++++++++--- tests/Abuse/Appwrite/TablesDBTest.php | 112 ++++++++++++++ 4 files changed, 300 insertions(+), 32 deletions(-) diff --git a/composer.json b/composer.json index 0138037271c..a79d7711e95 100755 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-redis": "*", "utopia-php/database": "^7.0.0", "utopia-php/pools": "2.*", - "appwrite/appwrite": "^26.0" + "appwrite/appwrite": "^27.1" }, "require-dev": { "phpunit/phpunit": "9.*", diff --git a/composer.lock b/composer.lock index 5f2aab6fd5e..a69036956d3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,23 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4aff8f896ffc6d7981242fdf9ce459cc", + "content-hash": "bfae95b93046381a457db8b4f33d5b35", "packages": [ + { + "name": "adhocore/jwt", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/adhocore/php-jwt.git", + "reference": "b7055ae9024a6627f19f9873b5db19b0f1dcc4e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adhocore/php-jwt/zipball/b7055ae9024a6627f19f9873b5db19b0f1dcc4e7", + "reference": "b7055ae9024a6627f19f9873b5db19b0f1dcc4e7", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ahc\\Jwt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jitendra Adhikari", + "email": "jiten.adhikary@gmail.com" + } + ], + "description": "Ultra lightweight JSON web token (JWT) library for PHP5.5+.", + "keywords": [ + "auth", + "json-web-token", + "jwt", + "jwt-auth", + "jwt-php", + "token" + ], + "support": { + "issues": "https://github.com/adhocore/php-jwt/issues", + "source": "https://github.com/adhocore/php-jwt/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://paypal.me/ji10", + "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" + } + ], + "time": "2026-05-30T07:58:15+00:00" + }, { "name": "appwrite/appwrite", - "version": "26.0.0", + "version": "27.1.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "bc26d0f0cfa4699de12d6af4df70ac0729ab7918" + "reference": "24685bc34928c0e5479d4977dc47ea4aa721f025" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/bc26d0f0cfa4699de12d6af4df70ac0729ab7918", - "reference": "bc26d0f0cfa4699de12d6af4df70ac0729ab7918", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/24685bc34928c0e5479d4977dc47ea4aa721f025", + "reference": "24685bc34928c0e5479d4977dc47ea4aa721f025", "shasum": "" }, "require": { + "adhocore/jwt": "^1.1", "ext-curl": "*", "ext-json": "*", "php": ">=8.2.0" @@ -43,10 +105,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/26.0.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/27.1.0", "url": "https://appwrite.io/support" }, - "time": "2026-06-17T04:32:07+00:00" + "time": "2026-07-24T06:10:52+00:00" }, { "name": "brick/math", diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index 7c95da4f45e..f568e067756 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -44,11 +44,17 @@ public function setup(): void } $this->createDatabase(); - $this->createTable(); - $this->createColumns(); - $this->waitForResourcesReady('columns'); - $this->createIndexes(); - $this->waitForResourcesReady('indexes'); + + if (! $this->createTable()) { + // The table is left over from a setup that did not run to completion, + // so some of its columns or indexes may be missing. Inline definitions + // only apply while the table is being created, so add them one by one. + $this->createColumns(); + $this->waitForResourcesReady('columns'); + $this->createIndexes(); + $this->waitForResourcesReady('indexes'); + } + $this->createLockTable(); } @@ -70,36 +76,98 @@ protected function createDatabase(): void ); } - protected function createTable(): void + /** + * Create the abuse table along with its columns and indexes in one request. + * + * Inline columns and indexes are created synchronously and come back + * available, so there is nothing to poll for afterwards. + * + * @return bool false when the table already existed + */ + protected function createTable(): bool { - $this->executeWithSilentError( - fn () => $this->tablesDB->createTable($this->databaseId, self::TABLE_ID, self::TABLE_NAME), + return $this->executeWithSilentError( + fn () => $this->tablesDB->createTable( + $this->databaseId, + self::TABLE_ID, + self::TABLE_NAME, + columns: $this->columnDefinitions(), + indexes: $this->indexDefinitions(), + ), 'table_already_exists' ); } - protected function createColumns(): void + /** + * Columns sent inline when the table is created. + * + * createColumns() repairs a table that already exists from the same list. + * + * @return array + */ + protected function columnDefinitions(): array + { + return [ + ['key' => 'key', 'type' => 'string', 'size' => 255, 'required' => true], + ['key' => 'time', 'type' => 'datetime', 'required' => true], + ['key' => 'count', 'type' => 'integer', 'required' => true, 'min' => 0, 'max' => PHP_INT_MAX], + ]; + } + + /** + * Indexes sent inline when the table is created. + * + * createIndexes() repairs a table that already exists from the same list. + * + * An inline definition names its columns under 'attributes', even though + * the index that comes back reports them under 'columns'. + * + * @return array}> + */ + protected function indexDefinitions(): array { - $columns = [ - fn () => $this->tablesDB->createStringColumn($this->databaseId, self::TABLE_ID, 'key', 255, true), - fn () => $this->tablesDB->createDatetimeColumn($this->databaseId, self::TABLE_ID, 'time', true), - fn () => $this->tablesDB->createIntegerColumn($this->databaseId, self::TABLE_ID, 'count', true, 0, PHP_INT_MAX) + return [ + ['key' => 'unique1', 'type' => (string) TablesDBIndexType::UNIQUE(), 'attributes' => ['key', 'time']], + ['key' => 'index2', 'type' => (string) TablesDBIndexType::KEY(), 'attributes' => ['time']], ]; + } + + /** + * Add the columns to a table that already exists, one endpoint per type. + */ + protected function createColumns(): void + { + foreach ($this->columnDefinitions() as $column) { + $key = $column['key']; + $required = $column['required']; + + $createColumnFunction = match ($column['type']) { + 'string' => fn () => $this->tablesDB->createStringColumn($this->databaseId, self::TABLE_ID, $key, $column['size'] ?? 0, $required), + 'datetime' => fn () => $this->tablesDB->createDatetimeColumn($this->databaseId, self::TABLE_ID, $key, $required), + 'integer' => fn () => $this->tablesDB->createIntegerColumn($this->databaseId, self::TABLE_ID, $key, $required, $column['min'] ?? null, $column['max'] ?? null), + default => throw new \Exception("No endpoint for column '{$key}'."), + }; - foreach ($columns as $createColumnFunction) { $this->executeWithSilentError($createColumnFunction, 'column_already_exists'); } } + /** + * Add the indexes to a table that already exists. + */ protected function createIndexes(): void { - $indexes = [ - fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'unique1', TablesDBIndexType::UNIQUE(), ['key', 'time']), - fn () => $this->tablesDB->createIndex($this->databaseId, self::TABLE_ID, 'index2', TablesDBIndexType::KEY(), ['time']) - ]; - - foreach ($indexes as $createIndexFunction) { - $this->executeWithSilentError($createIndexFunction, 'index_already_exists'); + foreach ($this->indexDefinitions() as $index) { + $this->executeWithSilentError( + fn () => $this->tablesDB->createIndex( + $this->databaseId, + self::TABLE_ID, + $index['key'], + TablesDBIndexType::from($index['type']), + $index['attributes'], + ), + 'index_already_exists' + ); } } @@ -115,8 +183,7 @@ protected function waitForResourcesReady(string $resourceType): void ? $this->tablesDB->listColumns($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->columns : $this->tablesDB->listIndexes($this->databaseId, self::TABLE_ID, [Query::notEqual('status', 'available'), Query::limit(1)])->indexes; - // Column models expose status as a ColumnStatus enum; index models as a plain string. Cast for both. - $resources = \array_filter($resources, fn ($resource) => (string) $resource->status !== 'available'); + $resources = \array_filter($resources, fn ($resource) => $this->resourceStatus($resource) !== 'available'); if (\count($resources) === 0) { return; @@ -128,6 +195,26 @@ protected function waitForResourcesReady(string $resourceType): void throw new \Exception("Failed to setup {$resourceType}."); } + /** + * Read the status off a listed column or index. + * + * List responses carry the raw payloads rather than hydrated models, so + * both an array and a model exposing a status are accepted. Column models + * hold a ColumnStatus enum, index models a plain string. + */ + protected function resourceStatus(mixed $resource): string + { + $status = null; + + if (\is_array($resource)) { + $status = $resource['status'] ?? null; + } elseif (\is_object($resource) && \property_exists($resource, 'status')) { + $status = $resource->status; + } + + return \is_scalar($status) || $status instanceof \Stringable ? (string) $status : ''; + } + protected function createLockTable(): void { $this->executeWithSilentError( @@ -136,14 +223,21 @@ protected function createLockTable(): void ); } - protected function executeWithSilentError(callable $callback, string $allowedErrorType): void + /** + * @return bool false when the call failed with the tolerated error + */ + protected function executeWithSilentError(callable $callback, string $allowedErrorType): bool { try { $callback(); + + return true; } catch (AppwriteException $err) { if ($err->getType() !== $allowedErrorType) { throw $err; } + + return false; } } diff --git a/tests/Abuse/Appwrite/TablesDBTest.php b/tests/Abuse/Appwrite/TablesDBTest.php index f45897dfc8f..b439cd6f675 100755 --- a/tests/Abuse/Appwrite/TablesDBTest.php +++ b/tests/Abuse/Appwrite/TablesDBTest.php @@ -3,6 +3,8 @@ namespace Utopia\Tests; use Appwrite\Client; +use Appwrite\Services\TablesDB as TablesDBService; +use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Abuse\Adapters\TimeLimit\Appwrite\TablesDB; @@ -37,6 +39,116 @@ public function getAdapter(string $key, int $limit, int $seconds): TimeLimit return new TablesDB($key, $limit, $seconds, self::$client, self::$databaseId); } + /** + * The schema is sent inline with the table, so assert it lands exactly as + * the dedicated per-column endpoints would have created it. + */ + public function testSetupCreatesSchema(): void + { + $tablesDB = new TablesDBService(self::$client); + + $columns = $this->indexByKey($tablesDB->listColumns(self::$databaseId, TablesDB::TABLE_ID)->columns); + + $this->assertCount(3, $columns); + + $this->assertSame('string', $columns['key']['type']); + $this->assertSame(255, $columns['key']['size']); + $this->assertTrue($columns['key']['required']); + + $this->assertSame('datetime', $columns['time']['type']); + $this->assertTrue($columns['time']['required']); + + $this->assertSame('integer', $columns['count']['type']); + $this->assertTrue($columns['count']['required']); + $this->assertEquals(0, $columns['count']['min']); + $this->assertEquals(PHP_INT_MAX, $columns['count']['max']); + + $indexes = $this->indexByKey($tablesDB->listIndexes(self::$databaseId, TablesDB::TABLE_ID)->indexes); + + $this->assertCount(2, $indexes); + + $this->assertSame('unique', $indexes['unique1']['type']); + $this->assertSame(['key', 'time'], $indexes['unique1']['columns']); + + $this->assertSame('key', $indexes['index2']['type']); + $this->assertSame(['time'], $indexes['index2']['columns']); + } + + /** + * A table left behind by a setup that did not run to completion is missing + * its columns and indexes, and they can no longer be sent inline. Setup has + * to fill them in one by one instead. + */ + public function testSetupRepairsPartiallyCreatedTable(): void + { + $databaseId = 'abuse-cicd-repair-' . \uniqid(); + $tablesDB = new TablesDBService(self::$client); + + $tablesDB->create($databaseId, TablesDB::DATABASE_NAME); + + try { + $tablesDB->createTable($databaseId, TablesDB::TABLE_ID, TablesDB::TABLE_NAME); + + $adapter = new TablesDB('repair-{{ip}}', 2, 60, self::$client, $databaseId); + $adapter->setup(); + + $columns = $this->indexByKey($tablesDB->listColumns($databaseId, TablesDB::TABLE_ID)->columns); + $indexes = $this->indexByKey($tablesDB->listIndexes($databaseId, TablesDB::TABLE_ID)->indexes); + + $this->assertCount(3, $columns); + $this->assertArrayHasKey('key', $columns); + $this->assertArrayHasKey('time', $columns); + $this->assertArrayHasKey('count', $columns); + + $this->assertCount(2, $indexes); + $this->assertArrayHasKey('unique1', $indexes); + $this->assertArrayHasKey('index2', $indexes); + + $adapter->setParam('{{ip}}', '0.0.0.20'); + $abuse = new Abuse($adapter); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), false); + $this->assertSame($abuse->check(), true); + } finally { + $tablesDB->delete($databaseId); + } + } + + /** + * Setup runs on every boot, so it has to be a no-op once the table is there. + */ + public function testSetupIsIdempotent(): void + { + $adapter = new TablesDB('', 1, 1, self::$client, self::$databaseId); + $adapter->setup(); + + $tablesDB = new TablesDBService(self::$client); + + $this->assertCount(3, $tablesDB->listColumns(self::$databaseId, TablesDB::TABLE_ID)->columns); + $this->assertCount(2, $tablesDB->listIndexes(self::$databaseId, TablesDB::TABLE_ID)->indexes); + } + + /** + * @param array $resources + * @return array> + */ + private function indexByKey(array $resources): array + { + $byKey = []; + + foreach ($resources as $resource) { + $this->assertIsArray($resource); + $this->assertSame('available', $resource['status']); + + $key = $resource['key']; + $this->assertIsString($key); + + $byKey[$key] = $resource; + } + + return $byKey; + } + public static function tearDownAfterClass(): void { } From 6b358a2eed887bb362a82a7cbdaeb65220923da5 Mon Sep 17 00:00:00 2001 From: Claudear <262350598+claudear@users.noreply.github.com> Date: Tue, 11 Aug 2026 06:37:55 +0000 Subject: [PATCH 355/470] fix(tests): read listed indexes as models, columns as raw payloads listIndexes hydrates each entry into a ColumnIndex; listColumns does not, because the SDK has no single model for the union of column types. The schema assertions treated both as arrays. Co-Authored-By: Claude Opus 5 --- .../Adapters/TimeLimit/Appwrite/TablesDB.php | 6 +-- tests/Abuse/Appwrite/TablesDBTest.php | 54 +++++++++++++------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php index f568e067756..fcd2563b1df 100644 --- a/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php +++ b/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php @@ -198,9 +198,9 @@ protected function waitForResourcesReady(string $resourceType): void /** * Read the status off a listed column or index. * - * List responses carry the raw payloads rather than hydrated models, so - * both an array and a model exposing a status are accepted. Column models - * hold a ColumnStatus enum, index models a plain string. + * A listed column arrives as the raw payload, since the SDK has no single + * model to hydrate the union of column types into, while a listed index + * arrives as a ColumnIndex. Accept either shape. */ protected function resourceStatus(mixed $resource): string { diff --git a/tests/Abuse/Appwrite/TablesDBTest.php b/tests/Abuse/Appwrite/TablesDBTest.php index b439cd6f675..42fd0a450e4 100755 --- a/tests/Abuse/Appwrite/TablesDBTest.php +++ b/tests/Abuse/Appwrite/TablesDBTest.php @@ -3,6 +3,7 @@ namespace Utopia\Tests; use Appwrite\Client; +use Appwrite\Models\ColumnIndex; use Appwrite\Services\TablesDB as TablesDBService; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; @@ -47,7 +48,7 @@ public function testSetupCreatesSchema(): void { $tablesDB = new TablesDBService(self::$client); - $columns = $this->indexByKey($tablesDB->listColumns(self::$databaseId, TablesDB::TABLE_ID)->columns); + $columns = $this->columnsByKey($tablesDB->listColumns(self::$databaseId, TablesDB::TABLE_ID)->columns); $this->assertCount(3, $columns); @@ -63,15 +64,15 @@ public function testSetupCreatesSchema(): void $this->assertEquals(0, $columns['count']['min']); $this->assertEquals(PHP_INT_MAX, $columns['count']['max']); - $indexes = $this->indexByKey($tablesDB->listIndexes(self::$databaseId, TablesDB::TABLE_ID)->indexes); + $indexes = $this->indexesByKey($tablesDB->listIndexes(self::$databaseId, TablesDB::TABLE_ID)->indexes); $this->assertCount(2, $indexes); - $this->assertSame('unique', $indexes['unique1']['type']); - $this->assertSame(['key', 'time'], $indexes['unique1']['columns']); + $this->assertSame('unique', $indexes['unique1']->type); + $this->assertSame(['key', 'time'], $indexes['unique1']->columns); - $this->assertSame('key', $indexes['index2']['type']); - $this->assertSame(['time'], $indexes['index2']['columns']); + $this->assertSame('key', $indexes['index2']->type); + $this->assertSame(['time'], $indexes['index2']->columns); } /** @@ -92,8 +93,8 @@ public function testSetupRepairsPartiallyCreatedTable(): void $adapter = new TablesDB('repair-{{ip}}', 2, 60, self::$client, $databaseId); $adapter->setup(); - $columns = $this->indexByKey($tablesDB->listColumns($databaseId, TablesDB::TABLE_ID)->columns); - $indexes = $this->indexByKey($tablesDB->listIndexes($databaseId, TablesDB::TABLE_ID)->indexes); + $columns = $this->columnsByKey($tablesDB->listColumns($databaseId, TablesDB::TABLE_ID)->columns); + $indexes = $this->indexesByKey($tablesDB->listIndexes($databaseId, TablesDB::TABLE_ID)->indexes); $this->assertCount(3, $columns); $this->assertArrayHasKey('key', $columns); @@ -129,21 +130,44 @@ public function testSetupIsIdempotent(): void } /** - * @param array $resources + * A listed column arrives as the raw payload: the SDK has no single model + * to hydrate the union of column types into. + * + * @param array $columns * @return array> */ - private function indexByKey(array $resources): array + private function columnsByKey(array $columns): array { $byKey = []; - foreach ($resources as $resource) { - $this->assertIsArray($resource); - $this->assertSame('available', $resource['status']); + foreach ($columns as $column) { + $this->assertIsArray($column); + $this->assertSame('available', $column['status']); - $key = $resource['key']; + $key = $column['key']; $this->assertIsString($key); - $byKey[$key] = $resource; + $byKey[$key] = $column; + } + + return $byKey; + } + + /** + * A listed index, unlike a column, arrives hydrated. + * + * @param array $indexes + * @return array + */ + private function indexesByKey(array $indexes): array + { + $byKey = []; + + foreach ($indexes as $index) { + $this->assertInstanceOf(ColumnIndex::class, $index); + $this->assertSame('available', $index->status); + + $byKey[$index->key] = $index; } return $byKey; From c3f41074d8a6842bd311d75bfb92188dbc9392a4 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:48:51 +0200 Subject: [PATCH 356/470] Merge pull request #122 from utopia-php/nats-client-tier1-hardening nats: bring the client to broad + deep feature parity --- composer.json | 3 +- docker-compose.yml | 29 +- src/Auth/CredentialsAuth.php | 4 +- src/Auth/NKeyAuth.php | 64 ++- src/Connection.php | 306 +++++++++-- src/ConnectionOptions.php | 18 + src/Exception/ObjectStoreException.php | 7 + src/JetStream/AccountInfo.php | 39 ++ src/JetStream/Consumer.php | 21 +- src/JetStream/ConsumerConfig.php | 27 + src/JetStream/ConsumerInfo.php | 17 +- src/JetStream/ConsumerLimits.php | 39 ++ src/JetStream/ExternalStream.php | 34 ++ src/JetStream/JetStream.php | 121 ++++- src/JetStream/JetStreamMessage.php | 13 + src/JetStream/OrderedConsumer.php | 130 +++++ src/JetStream/Placement.php | 37 ++ src/JetStream/PushSubscription.php | 88 ++++ src/JetStream/Republish.php | 36 ++ src/JetStream/SequenceInfo.php | 23 + src/JetStream/StreamConfig.php | 70 +++ src/JetStream/StreamMessage.php | 43 ++ src/JetStream/StreamSource.php | 64 +++ src/JetStream/StreamState.php | 4 + src/JetStream/SubjectTransform.php | 32 ++ src/KeyValue/KeyValue.php | 299 +++++++++++ src/KeyValue/KeyValueConfig.php | 1 - src/KeyValue/KeyValueWatchOptions.php | 25 + src/ObjectStore/ObjectLink.php | 41 ++ src/ObjectStore/ObjectMeta.php | 88 ++++ src/ObjectStore/ObjectStore.php | 491 ++++++++++++++++++ src/ObjectStore/ObjectStoreConfig.php | 45 ++ src/Services/Endpoint.php | 68 +++ src/Services/Group.php | 59 +++ src/Services/Service.php | 261 ++++++++++ src/Services/ServiceException.php | 24 + src/Subscription.php | 51 +- src/Transport/SwooleTransport.php | 184 +++++++ src/Transport/TcpTransport.php | 23 +- src/Transport/TlsTransport.php | 24 +- src/Transport/WebSocketTransport.php | 280 ++++++++++ tests/E2E/HardeningTest.php | 80 +++ tests/E2E/JetStreamExtraTest.php | 210 ++++++++ tests/E2E/JetStreamParityTest.php | 259 +++++++++ tests/E2E/KeyValueWatchOptionsTest.php | 201 +++++++ tests/E2E/KeyValueWatchTest.php | 137 +++++ tests/E2E/ObjectStoreExtraTest.php | 180 +++++++ tests/E2E/ObjectStoreTest.php | 151 ++++++ tests/E2E/OrderedConsumerTest.php | 142 +++++ tests/E2E/RequestManyTest.php | 113 ++++ tests/E2E/ServiceExtrasTest.php | 156 ++++++ tests/E2E/ServiceTest.php | 94 ++++ tests/E2E/TlsTest.php | 70 +++ tests/E2E/WebSocketTest.php | 68 +++ tests/Unit/Auth/NKeyAuthTest.php | 133 +++++ tests/Unit/ConnectionExtrasTest.php | 113 ++++ tests/Unit/ConnectionProtocolTest.php | 114 ++++ tests/Unit/ConnectionTlsAndAuthTest.php | 100 ++++ tests/Unit/JetStream/ConfigTest.php | 75 +++ tests/Unit/ReconnectBackoffTest.php | 62 +++ tests/Unit/SubscriptionSlowConsumerTest.php | 111 ++++ tests/Unit/Support/FakeTransport.php | 131 +++++ tests/Unit/Support/PartialWriteStream.php | 44 ++ .../Unit/Transport/TcpTransportWriteTest.php | 55 ++ tests/fixtures/certs/ca-key.pem | 28 + tests/fixtures/certs/ca.pem | 19 + tests/fixtures/certs/client-cert.pem | 19 + tests/fixtures/certs/client-key.pem | 28 + tests/fixtures/certs/server-cert.pem | 20 + tests/fixtures/certs/server-key.pem | 28 + tests/fixtures/nats-tls.conf | 14 + tests/fixtures/nats-ws.conf | 8 + 72 files changed, 6131 insertions(+), 65 deletions(-) create mode 100644 src/Exception/ObjectStoreException.php create mode 100644 src/JetStream/AccountInfo.php create mode 100644 src/JetStream/ConsumerLimits.php create mode 100644 src/JetStream/ExternalStream.php create mode 100644 src/JetStream/OrderedConsumer.php create mode 100644 src/JetStream/Placement.php create mode 100644 src/JetStream/PushSubscription.php create mode 100644 src/JetStream/Republish.php create mode 100644 src/JetStream/SequenceInfo.php create mode 100644 src/JetStream/StreamMessage.php create mode 100644 src/JetStream/StreamSource.php create mode 100644 src/JetStream/SubjectTransform.php create mode 100644 src/KeyValue/KeyValueWatchOptions.php create mode 100644 src/ObjectStore/ObjectLink.php create mode 100644 src/ObjectStore/ObjectMeta.php create mode 100644 src/ObjectStore/ObjectStore.php create mode 100644 src/ObjectStore/ObjectStoreConfig.php create mode 100644 src/Services/Endpoint.php create mode 100644 src/Services/Group.php create mode 100644 src/Services/Service.php create mode 100644 src/Services/ServiceException.php create mode 100644 src/Transport/SwooleTransport.php create mode 100644 src/Transport/WebSocketTransport.php create mode 100644 tests/E2E/HardeningTest.php create mode 100644 tests/E2E/JetStreamExtraTest.php create mode 100644 tests/E2E/JetStreamParityTest.php create mode 100644 tests/E2E/KeyValueWatchOptionsTest.php create mode 100644 tests/E2E/KeyValueWatchTest.php create mode 100644 tests/E2E/ObjectStoreExtraTest.php create mode 100644 tests/E2E/ObjectStoreTest.php create mode 100644 tests/E2E/OrderedConsumerTest.php create mode 100644 tests/E2E/RequestManyTest.php create mode 100644 tests/E2E/ServiceExtrasTest.php create mode 100644 tests/E2E/ServiceTest.php create mode 100644 tests/E2E/TlsTest.php create mode 100644 tests/E2E/WebSocketTest.php create mode 100644 tests/Unit/Auth/NKeyAuthTest.php create mode 100644 tests/Unit/ConnectionExtrasTest.php create mode 100644 tests/Unit/ConnectionProtocolTest.php create mode 100644 tests/Unit/ConnectionTlsAndAuthTest.php create mode 100644 tests/Unit/JetStream/ConfigTest.php create mode 100644 tests/Unit/ReconnectBackoffTest.php create mode 100644 tests/Unit/SubscriptionSlowConsumerTest.php create mode 100644 tests/Unit/Support/FakeTransport.php create mode 100644 tests/Unit/Support/PartialWriteStream.php create mode 100644 tests/Unit/Transport/TcpTransportWriteTest.php create mode 100644 tests/fixtures/certs/ca-key.pem create mode 100644 tests/fixtures/certs/ca.pem create mode 100644 tests/fixtures/certs/client-cert.pem create mode 100644 tests/fixtures/certs/client-key.pem create mode 100644 tests/fixtures/certs/server-cert.pem create mode 100644 tests/fixtures/certs/server-key.pem create mode 100644 tests/fixtures/nats-tls.conf create mode 100644 tests/fixtures/nats-ws.conf diff --git a/composer.json b/composer.json index ad22ec1c1c1..85d8d38da47 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "ext-json": "*" }, "suggest": { - "ext-sodium": "Required for NKey and JWT credential authentication" + "ext-sodium": "Required for NKey and JWT credential authentication", + "ext-swoole": "Required for the coroutine-native SwooleTransport" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml index 5f5386dc9f2..5431b73a16f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: nats: - image: nats:2.10-alpine + image: nats:2.11-alpine command: ["-js", "-m", "8222"] ports: - "14222:4222" @@ -9,3 +9,30 @@ services: interval: 2s timeout: 2s retries: 30 + + nats-tls: + image: nats:2.11-alpine + command: ["-c", "/nats.conf"] + volumes: + - ./tests/fixtures/certs:/certs:ro + - ./tests/fixtures/nats-tls.conf:/nats.conf:ro + ports: + - "14223:4222" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8222/healthz"] + interval: 2s + timeout: 2s + retries: 30 + + nats-ws: + image: nats:2.11-alpine + command: ["-c", "/nats.conf"] + volumes: + - ./tests/fixtures/nats-ws.conf:/nats.conf:ro + ports: + - "14224:8080" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:8222/healthz"] + interval: 2s + timeout: 2s + retries: 30 diff --git a/src/Auth/CredentialsAuth.php b/src/Auth/CredentialsAuth.php index 04d3e7df96b..2e1e0e9afac 100644 --- a/src/Auth/CredentialsAuth.php +++ b/src/Auth/CredentialsAuth.php @@ -33,8 +33,8 @@ public function __construct(string $credentialsFile) } $this->jwt = $jwt; - // We need the public key from the seed for NKeyAuth, but for creds auth - // we derive it. For now, pass empty and override authenticate(). + // NKeyAuth derives the real public key from the seed when none is given, + // so authenticate() below sends the correct nkey rather than an empty one. $this->nkeyAuth = new NKeyAuth('', $seed); } diff --git a/src/Auth/NKeyAuth.php b/src/Auth/NKeyAuth.php index 916e328e7c3..31a76eb4dc1 100644 --- a/src/Auth/NKeyAuth.php +++ b/src/Auth/NKeyAuth.php @@ -29,11 +29,73 @@ public function authenticate(?string $nonce = null): array $signature = sodium_crypto_sign_detached($nonce, $secretKey); return [ - 'nkey' => $this->publicKey, + 'nkey' => $this->publicKey(), 'sig' => $this->base32Encode($signature), ]; } + /** + * The public NKey. When constructed without an explicit public key (e.g. from + * a credentials file), it is derived from the seed so the server receives the + * real ed25519 public key rather than an empty string. + */ + public function publicKey(): string + { + if ($this->publicKey !== '') { + return $this->publicKey; + } + + return $this->derivePublicKey(); + } + + private function derivePublicKey(): string + { + $decoded = $this->base32Decode($this->seed); + if (\strlen($decoded) < 4) { + throw new AuthenticationException('Invalid NKey seed'); + } + + // The two-byte seed prefix encodes both the seed marker and the public + // key's role byte (see nkeys EncodeSeed): b1 = SEED | (role >> 5), + // b2 = (role & 31) << 3. Recover the role so the derived public key + // carries the correct prefix (e.g. 'U' for a user). + $b1 = \ord($decoded[0]); + $b2 = \ord($decoded[1]); + $role = (($b1 & 7) << 5) | (($b2 >> 3) & 31); + + $rawSeed = substr($decoded, 2, -2); + $keyPair = sodium_crypto_sign_seed_keypair($rawSeed); + $publicKey = sodium_crypto_sign_publickey($keyPair); + + return $this->encodePublicKey($role, $publicKey); + } + + private function encodePublicKey(int $role, string $publicKey): string + { + $raw = \chr($role) . $publicKey; + $crc = $this->crc16($raw); + // CRC is appended little-endian, matching the nkeys encoding. + $raw .= \chr($crc & 0xFF) . \chr(($crc >> 8) & 0xFF); + + return $this->base32Encode($raw); + } + + /** + * CRC-16/XMODEM (poly 0x1021, init 0x0000), as used by NATS nkeys. + */ + private function crc16(string $data): int + { + $crc = 0; + for ($i = 0, $len = \strlen($data); $i < $len; $i++) { + $crc ^= \ord($data[$i]) << 8; + for ($j = 0; $j < 8; $j++) { + $crc = ($crc & 0x8000) !== 0 ? (($crc << 1) ^ 0x1021) & 0xFFFF : ($crc << 1) & 0xFFFF; + } + } + + return $crc & 0xFFFF; + } + private function decodeSeed(string $seed): string { $decoded = $this->base32Decode($seed); diff --git a/src/Connection.php b/src/Connection.php index 463243b4ee1..d8238eb304a 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -23,6 +23,7 @@ use Utopia\NATS\Transport\TcpTransport; use Utopia\NATS\Transport\TlsTransport; use Utopia\NATS\Transport\Transport; +use Utopia\NATS\Transport\WebSocketTransport; final class Connection { @@ -59,8 +60,10 @@ final class Connection // Reconnection /** @var list */ private array $serverPool = []; + private string $currentServer = ''; /** @var list */ private array $pendingBuffer = []; + private int $pendingBufferBytes = 0; private function __construct() { @@ -95,14 +98,22 @@ public function publish(string $subject, string $data = '', ?string $replyTo = n { $this->ensureConnected(); - if (isset($this->serverInfo) && \strlen($data) > $this->serverInfo->maxPayload) { + $hasHeaders = $headers instanceof \Utopia\NATS\Headers && $headers->all() !== []; + + if ($hasHeaders && isset($this->serverInfo) && !$this->serverInfo->headersSupported) { + throw new ProtocolException('Server does not support message headers'); + } + + $headerWire = $hasHeaders ? $headers->toWire() : ''; + // The header block counts against the server's max payload budget. + $wireSize = \strlen($headerWire) + \strlen($data); + if (isset($this->serverInfo) && $wireSize > $this->serverInfo->maxPayload) { throw new MaxPayloadException( - 'Payload size ' . \strlen($data) . " exceeds server maximum of {$this->serverInfo->maxPayload}", + "Payload size {$wireSize} exceeds server maximum of {$this->serverInfo->maxPayload}", ); } - if ($headers instanceof \Utopia\NATS\Headers && $headers->all() !== []) { - $headerWire = $headers->toWire(); + if ($hasHeaders) { $cmd = $this->writer->hpub($subject, $headerWire, $data, $replyTo); } else { $cmd = $this->writer->pub($subject, $data, $replyTo); @@ -116,7 +127,15 @@ public function subscribe(string $subject, ?\Closure $callback = null, ?string $ $this->ensureConnected(); $sid = (string) $this->nextSid++; - $sub = new Subscription($sid, $subject, $queue, $callback); + $sub = new Subscription( + $sid, + $subject, + $queue, + $callback, + $this->options->subPendingMsgsLimit, + $this->options->subPendingBytesLimit, + $this->options->onSlowConsumer, + ); $sub->setConnection($this); $this->subscriptions[$sid] = $sub; @@ -182,6 +201,63 @@ public function request(string $subject, string $data = '', ?float $timeout = nu return $msg; } + /** + * Scatter-gather request: publish once and collect every reply until a stop + * condition is met (ADR-47). A 503 "no responders" reply means zero + * responders and yields an empty list. + * + * @param array{max?: int, timeout?: float, stall?: float} $opts + * max stop after this many replies + * timeout overall deadline in seconds (defaults to the request timeout) + * stall stop when no new reply arrives within this many seconds + * @return list + */ + public function requestMany(string $subject, string $data = '', array $opts = []): array + { + $this->ensureConnected(); + + $max = $opts['max'] ?? null; + $timeout = $opts['timeout'] ?? $this->options->requestTimeout; + $stall = $opts['stall'] ?? null; + + // Dedicated inbox subscription so replies never touch the mux inbox used + // by the single-reply request(). + $inbox = Inbox::create($this->options->inboxPrefix); + $sub = $this->subscribe($inbox); + $this->publish($subject, $data, $inbox); + + $deadline = microtime(true) + $timeout; + $messages = []; + + while ($max === null || \count($messages) < $max) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + break; + } + + // A stall window caps how long we wait for the next reply; whichever + // of stall/overall-deadline is sooner bounds this iteration. + $wait = $stall !== null ? min($remaining, $stall) : $remaining; + + $msg = $sub->nextMessage($wait); + if (!$msg instanceof Message) { + // Overall deadline or stall window elapsed with no new reply. + break; + } + + // 503 no-responders: zero responders, return whatever we have (none). + if ($msg->headers instanceof Headers && $msg->headers->getStatus() === '503') { + break; + } + + $messages[] = $msg; + } + + $this->unsubscribe($sub); + + return $messages; + } + public function newInbox(): string { return Inbox::create($this->options->inboxPrefix); @@ -269,24 +345,34 @@ public function drain(?float $timeout = null): void $this->status = self::STATUS_DRAINING; $timeout ??= $this->options->drainTimeout; - // Unsub all subscriptions + // Unsub all subscriptions, then send a PING. The server processes the + // UNSUBs and flushes any already-queued messages ahead of the PONG, so + // receiving that PONG is a deterministic barrier: everything the server + // had for us has arrived, and nothing new will. This replaces the old + // pure-timeout drain. foreach ($this->subscriptions as $sub) { $this->send($this->writer->unsub($sub->sid)); } + $this->send($this->writer->ping()); - // Process remaining messages $deadline = microtime(true) + $timeout; - while ($this->subscriptions !== []) { + while (true) { $remaining = $deadline - microtime(true); if ($remaining <= 0) { break; } try { - $this->processMessage($remaining); - } catch (TimeoutException) { + [$op, $data] = $this->parser->next($remaining); + } catch (TimeoutException|ConnectionException) { + break; + } + + if ($op === ServerOp::Pong) { break; } + + $this->dispatchOp($op, $data); } $this->close(); @@ -374,12 +460,12 @@ private function connectToServer(string $url): void $scheme = $parsed['scheme']; // Create transport - if ($scheme === 'tls' || $this->options->tls) { - $this->transport = new TlsTransport([ - 'cafile' => $this->options->tlsCaFile, - 'local_cert' => $this->options->tlsCertFile, - 'local_pk' => $this->options->tlsKeyFile, - ]); + if ($this->options->transportFactory instanceof \Closure) { + $this->transport = ($this->options->transportFactory)($scheme); + } elseif ($scheme === 'ws' || $scheme === 'wss') { + $this->transport = new WebSocketTransport($scheme === 'wss', $this->tlsOptions()); + } elseif ($scheme === 'tls' || $this->options->tls) { + $this->transport = new TlsTransport($this->tlsOptions()); } else { $this->transport = new TcpTransport(); } @@ -401,13 +487,15 @@ private function connectToServer(string $url): void } } - // TLS upgrade if required by server - if ($this->serverInfo->tlsRequired && $scheme !== 'tls' && !$this->options->tls) { - $this->transport->upgradeTls([ - 'cafile' => $this->options->tlsCaFile, - 'local_cert' => $this->options->tlsCertFile, - 'local_pk' => $this->options->tlsKeyFile, - ]); + // TLS upgrade over a plaintext TCP connection: when the server requires + // TLS, or when the caller opted in and the server advertises it is + // available. A TlsTransport / custom transport handles its own TLS. + if ($this->transport instanceof TcpTransport && $scheme !== 'tls') { + $wantsUpgrade = $this->serverInfo->tlsRequired + || ($this->options->tls && $this->serverInfo->tlsAvailable); + if ($wantsUpgrade) { + $this->transport->upgradeTls($this->tlsOptions()); + } } // Send CONNECT @@ -442,10 +530,13 @@ private function connectToServer(string $url): void } $this->lastPingTime = microtime(true); + $this->currentServer = $url; } private function buildConnectPayload(): array { + $headersSupported = $this->serverInfo->headersSupported; + $payload = [ 'verbose' => $this->options->verbose, 'pedantic' => $this->options->pedantic, @@ -453,8 +544,10 @@ private function buildConnectPayload(): array 'version' => self::CLIENT_VERSION, 'protocol' => 1, 'echo' => $this->options->echo, - 'headers' => true, - 'no_responders' => true, + // Only negotiate headers (and the header-based no_responders reply) + // when the server advertises support for them. + 'headers' => $headersSupported, + 'no_responders' => $headersSupported, ]; if ($this->options->name !== '') { @@ -462,8 +555,18 @@ private function buildConnectPayload(): array } $authFields = $this->auth->authenticate($this->serverInfo->nonce); + $payload = array_merge($payload, $authFields); + + // Dynamic providers are resolved on every (re)connect so refreshed + // tokens/JWTs take effect without rebuilding the connection. + if ($this->options->tokenProvider instanceof \Closure) { + $payload['auth_token'] = (string) ($this->options->tokenProvider)(); + } + if ($this->options->jwtProvider instanceof \Closure) { + $payload['jwt'] = (string) ($this->options->jwtProvider)(); + } - return array_merge($payload, $authFields); + return $payload; } private function dispatchOp(ServerOp $op, mixed $data): ?Message @@ -536,27 +639,71 @@ private function handleError(mixed $data): never ($this->options->onError)(new NatsException($message)); } - if (stripos($message, 'permissions violation') !== false) { - throw new PermissionException($message); - } - if (stripos($message, 'authorization') !== false || stripos($message, 'authentication') !== false) { - throw new AuthenticationException($message); - } - if (stripos($message, 'maximum payload') !== false) { - throw new MaxPayloadException($message); - } + throw self::mapServerError($message); + } - throw new ProtocolException("Server error: {$message}"); + /** + * Map a server -ERR string to a typed exception (ADR-7). Pure so the mapping + * can be unit tested without a live connection. + */ + public static function mapServerError(string $message): NatsException + { + $lower = strtolower($message); + + return match (true) { + str_contains($lower, 'permissions violation') => new PermissionException($message), + str_contains($lower, 'authorization violation'), + str_contains($lower, 'authentication expired'), + str_contains($lower, 'authorization'), + str_contains($lower, 'authentication') => new AuthenticationException($message), + str_contains($lower, 'maximum payload') => new MaxPayloadException($message), + default => new ProtocolException("Server error: {$message}"), + }; } private function handleInfo(mixed $data): null { if (\is_array($data)) { $this->serverInfo = ServerInfo::fromArray($data); + + // Async INFO may advertise additional cluster members; fold any new + // ones into the pool so failover has somewhere to go. + foreach ($this->serverInfo->connectUrls as $connectUrl) { + $normalized = $this->normalizeUrl($connectUrl); + if (!\in_array($normalized, $this->serverPool, true)) { + $this->serverPool[] = $normalized; + } + } + + // Lame-duck mode (ADR-5): the server is draining and will close the + // connection. Notify the caller and move to a different server if we + // know of one. + if (($data['ldm'] ?? false) === true) { + $this->handleLameDuck(); + } } return null; } + private function handleLameDuck(): void + { + if ($this->options->onLameDuck instanceof \Closure) { + ($this->options->onLameDuck)(); + } + + $others = array_values(array_filter( + $this->serverPool, + fn(string $url): bool => $url !== $this->currentServer, + )); + + // Only proactively reconnect when a different server is available; + // otherwise ride out the current connection until it is closed. + if ($others !== [] && $this->options->allowReconnect) { + $this->serverPool = [...$others, $this->currentServer]; + $this->attemptReconnect(); + } + } + private function checkPings(): void { if ($this->status !== self::STATUS_CONNECTED) { @@ -603,9 +750,14 @@ private function attemptReconnect(): void } for ($attempt = 0; $attempt < $this->options->maxReconnectAttempts; $attempt++) { - // Wait with jitter before reconnecting + // Exponential backoff (capped) plus jitter before reconnecting. if ($attempt > 0) { - $wait = $this->options->reconnectWait + (lcg_value() * $this->options->reconnectJitter); + $backoff = self::reconnectBackoff( + $attempt, + $this->options->reconnectWait, + $this->options->maxReconnectWait, + ); + $wait = $backoff + (lcg_value() * $this->options->reconnectJitter); usleep((int) ($wait * 1_000_000)); } @@ -623,10 +775,12 @@ private function attemptReconnect(): void } // Flush any buffered publishes - foreach ($this->pendingBuffer as $cmd) { + $buffered = $this->pendingBuffer; + $this->pendingBuffer = []; + $this->pendingBufferBytes = 0; + foreach ($buffered as $cmd) { $this->send($cmd); } - $this->pendingBuffer = []; if ($this->options->onReconnect instanceof \Closure) { ($this->options->onReconnect)(); @@ -653,7 +807,7 @@ private function ensureConnected(): void private function send(string $data): void { if ($this->status === self::STATUS_RECONNECTING) { - $this->pendingBuffer[] = $data; + $this->bufferPending($data); return; } @@ -661,7 +815,7 @@ private function send(string $data): void $this->transport->write($data); } catch (ConnectionException $e) { if ($this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { - $this->pendingBuffer[] = $data; + $this->bufferPending($data); $this->attemptReconnect(); return; } @@ -669,6 +823,52 @@ private function send(string $data): void } } + /** + * Buffer a command while reconnecting, enforcing the reconnect buffer cap. + * Commands that would exceed the cap are dropped (and reported via onError) + * rather than growing the buffer without bound. + */ + private function bufferPending(string $data): void + { + if (!self::reconnectBufferAccepts($this->pendingBufferBytes, \strlen($data), $this->options->reconnectBufSize)) { + if ($this->options->onError instanceof \Closure) { + ($this->options->onError)(new NatsException('Reconnect buffer full; dropping pending message')); + } + return; + } + + $this->pendingBuffer[] = $data; + $this->pendingBufferBytes += \strlen($data); + } + + /** + * Exponential reconnect backoff (in seconds), capped. Attempt 0 waits 0s + * (the first reconnect is immediate); subsequent attempts grow by $factor. + */ + public static function reconnectBackoff(int $attempt, float $base, float $cap, float $factor = 2.0): float + { + if ($attempt <= 0) { + return 0.0; + } + + $delay = $base * ($factor ** ($attempt - 1)); + + return min($delay, $cap); + } + + /** + * Whether $incomingBytes may be appended to the reconnect buffer without + * exceeding $cap. A non-positive cap disables buffering entirely. + */ + public static function reconnectBufferAccepts(int $currentBytes, int $incomingBytes, int $cap): bool + { + if ($cap <= 0) { + return false; + } + + return ($currentBytes + $incomingBytes) <= $cap; + } + private function ensureInboxSub(): void { if ($this->inboxSub instanceof \Utopia\NATS\Subscription) { @@ -737,6 +937,28 @@ private function buildServerPool(ConnectionOptions $options): array return $servers; } + /** + * TLS context options shared by the initial TLS connect and STARTTLS upgrade. + * + * @return array + */ + private function tlsOptions(): array + { + $opts = [ + 'cafile' => $this->options->tlsCaFile, + 'local_cert' => $this->options->tlsCertFile, + 'local_pk' => $this->options->tlsKeyFile, + 'verify_peer' => $this->options->tlsVerify, + 'verify_peer_name' => $this->options->tlsVerify, + ]; + + if ($this->options->tlsServerName !== null) { + $opts['peer_name'] = $this->options->tlsServerName; + } + + return $opts; + } + private function normalizeUrl(string $url): string { if (!preg_match('#^(nats|tls|ws|wss)://#', $url)) { diff --git a/src/ConnectionOptions.php b/src/ConnectionOptions.php index aedbb294f3c..56719d0c40a 100644 --- a/src/ConnectionOptions.php +++ b/src/ConnectionOptions.php @@ -27,11 +27,22 @@ public function __construct( public readonly ?string $tlsCaFile = null, public readonly ?string $tlsCertFile = null, public readonly ?string $tlsKeyFile = null, + public readonly bool $tlsVerify = true, + public readonly ?string $tlsServerName = null, + // Dynamic auth providers, resolved on every (re)connect so tokens can refresh. + public readonly ?\Closure $tokenProvider = null, + public readonly ?\Closure $jwtProvider = null, // Reconnection public readonly bool $allowReconnect = true, public readonly int $maxReconnectAttempts = 60, public readonly float $reconnectWait = 2.0, + public readonly float $maxReconnectWait = 8.0, public readonly float $reconnectJitter = 0.1, + // Max bytes buffered for pending publishes while reconnecting; excess is dropped. + public readonly int $reconnectBufSize = 8_388_608, + // Slow-consumer limits per subscription (pending messages / bytes). + public readonly int $subPendingMsgsLimit = 65536, + public readonly int $subPendingBytesLimit = 67_108_864, // Timeouts public readonly float $connectTimeout = 2.0, public readonly float $requestTimeout = 5.0, @@ -50,6 +61,13 @@ public function __construct( public readonly ?\Closure $onReconnect = null, public readonly ?\Closure $onClose = null, public readonly ?\Closure $onError = null, + // Fired with the Subscription when it exceeds its pending limits. + public readonly ?\Closure $onSlowConsumer = null, + // Fired when the server signals lame-duck mode (async INFO with "ldm": true). + public readonly ?\Closure $onLameDuck = null, + // Transport: fn(string $scheme): Transport. Defaults to the stream-based + // Tcp/Tls transports; inject to use a coroutine-native transport (e.g. Swoole). + public readonly ?\Closure $transportFactory = null, ) { $this->servers = \is_string($servers) ? [$servers] : $servers; } diff --git a/src/Exception/ObjectStoreException.php b/src/Exception/ObjectStoreException.php new file mode 100644 index 00000000000..2816f68d18b --- /dev/null +++ b/src/Exception/ObjectStoreException.php @@ -0,0 +1,7 @@ + $limits + * @param array $raw + */ + public function __construct( + public readonly int $memory, + public readonly int $storage, + public readonly int $streams, + public readonly int $consumers, + public readonly array $limits, + public readonly int $apiTotal, + public readonly int $apiErrors, + public readonly ?string $domain = null, + public readonly array $raw = [], + ) {} + + public static function fromArray(array $data): self + { + return new self( + memory: $data['memory'] ?? 0, + storage: $data['storage'] ?? 0, + streams: $data['streams'] ?? 0, + consumers: $data['consumers'] ?? 0, + limits: $data['limits'] ?? [], + apiTotal: $data['api']['total'] ?? 0, + apiErrors: $data['api']['errors'] ?? 0, + domain: $data['domain'] ?? null, + raw: $data, + ); + } +} diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index 07d413b09c4..63d28880bf2 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -19,15 +19,23 @@ public function __construct( /** * Fetch a batch of messages from the consumer. */ - public function fetch(int $batch = 1, ?float $timeout = null): MessageBatch + public function fetch(int $batch = 1, ?float $timeout = null, bool $noWait = false, ?int $maxBytes = null): MessageBatch { $timeout ??= 5.0; $requestSubject = "{$this->apiPrefix}.CONSUMER.MSG.NEXT.{$this->stream}.{$this->getName()}"; - $payload = json_encode([ + $request = [ 'batch' => $batch, 'expires' => StreamConfig::secondsToNanos($timeout), - ], JSON_THROW_ON_ERROR); + ]; + if ($noWait) { + $request['no_wait'] = true; + } + if ($maxBytes !== null) { + $request['max_bytes'] = $maxBytes; + } + + $payload = json_encode($request, JSON_THROW_ON_ERROR); $inbox = $this->conn->newInbox(); $sub = $this->conn->subscribe($inbox); @@ -54,6 +62,13 @@ public function fetch(int $batch = 1, ?float $timeout = null): MessageBatch if (\in_array($status, ['408', '404', '409'], true)) { break; } + // 100 = flow control / idle heartbeat: keep-alive, not data. + if ($status === '100') { + if ($msg->replyTo !== null && $msg->replyTo !== '') { + $this->conn->publish($msg->replyTo, ''); + } + continue; + } } $messageBatch->addMessage($msg); diff --git a/src/JetStream/ConsumerConfig.php b/src/JetStream/ConsumerConfig.php index 230202c6060..bd97c10218a 100644 --- a/src/JetStream/ConsumerConfig.php +++ b/src/JetStream/ConsumerConfig.php @@ -9,7 +9,9 @@ final class ConsumerConfig /** * @param float|null $ackWait Ack wait in seconds * @param float|null $inactiveThreshold Inactive threshold in seconds + * @param float|null $idleHeartbeat Idle heartbeat interval in seconds (push consumers) * @param list|null $filterSubjects + * @param array|null $metadata Arbitrary key-value metadata (ADR-33) */ public function __construct( public readonly ?string $name = null, @@ -31,6 +33,11 @@ public function __construct( public readonly ?int $maxBytes = null, public readonly bool $memStorage = false, public readonly ?int $numReplicas = null, + public readonly ?string $deliverSubject = null, + public readonly ?string $deliverGroup = null, + public readonly bool $flowControl = false, + public readonly ?float $idleHeartbeat = null, + public readonly ?array $metadata = null, ) {} public function toArray(): array @@ -89,6 +96,21 @@ public function toArray(): array if ($this->numReplicas !== null) { $data['num_replicas'] = $this->numReplicas; } + if ($this->deliverSubject !== null) { + $data['deliver_subject'] = $this->deliverSubject; + } + if ($this->deliverGroup !== null) { + $data['deliver_group'] = $this->deliverGroup; + } + if ($this->flowControl) { + $data['flow_control'] = true; + } + if ($this->idleHeartbeat !== null) { + $data['idle_heartbeat'] = StreamConfig::secondsToNanos($this->idleHeartbeat); + } + if ($this->metadata !== null) { + $data['metadata'] = $this->metadata; + } return $data; } @@ -115,6 +137,11 @@ public static function fromArray(array $data): self maxBytes: $data['max_bytes'] ?? null, memStorage: $data['mem_storage'] ?? false, numReplicas: $data['num_replicas'] ?? null, + deliverSubject: $data['deliver_subject'] ?? null, + deliverGroup: $data['deliver_group'] ?? null, + flowControl: $data['flow_control'] ?? false, + idleHeartbeat: isset($data['idle_heartbeat']) ? StreamConfig::nanosToSeconds($data['idle_heartbeat']) : null, + metadata: $data['metadata'] ?? null, ); } } diff --git a/src/JetStream/ConsumerInfo.php b/src/JetStream/ConsumerInfo.php index de9becc22d4..8bf83ce6d67 100644 --- a/src/JetStream/ConsumerInfo.php +++ b/src/JetStream/ConsumerInfo.php @@ -6,6 +6,9 @@ final class ConsumerInfo { + /** + * @param array|null $metadata Consumer metadata (ADR-33) + */ public function __construct( public readonly string $streamName, public readonly string $name, @@ -15,19 +18,31 @@ public function __construct( public readonly int $numRedelivered, public readonly int $numWaiting, public readonly int $numPending, + public readonly SequenceInfo $delivered, + public readonly SequenceInfo $ackFloor, + public readonly bool $pushBound = false, + public readonly ?string $cluster = null, + public readonly ?array $metadata = null, ) {} public static function fromArray(array $data): self { + $config = ConsumerConfig::fromArray($data['config'] ?? []); + return new self( streamName: $data['stream_name'] ?? '', name: $data['name'] ?? '', - config: ConsumerConfig::fromArray($data['config'] ?? []), + config: $config, created: $data['created'] ?? '', numAckPending: $data['num_ack_pending'] ?? 0, numRedelivered: $data['num_redelivered'] ?? 0, numWaiting: $data['num_waiting'] ?? 0, numPending: $data['num_pending'] ?? 0, + delivered: SequenceInfo::fromArray($data['delivered'] ?? []), + ackFloor: SequenceInfo::fromArray($data['ack_floor'] ?? []), + pushBound: $data['push_bound'] ?? false, + cluster: isset($data['cluster']['name']) ? (string) $data['cluster']['name'] : null, + metadata: $config->metadata, ); } } diff --git a/src/JetStream/ConsumerLimits.php b/src/JetStream/ConsumerLimits.php new file mode 100644 index 00000000000..e977aece603 --- /dev/null +++ b/src/JetStream/ConsumerLimits.php @@ -0,0 +1,39 @@ +inactiveThreshold !== null) { + $data['inactive_threshold'] = StreamConfig::secondsToNanos($this->inactiveThreshold); + } + if ($this->maxAckPending !== null) { + $data['max_ack_pending'] = $this->maxAckPending; + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + inactiveThreshold: isset($data['inactive_threshold']) + ? StreamConfig::nanosToSeconds($data['inactive_threshold']) + : null, + maxAckPending: $data['max_ack_pending'] ?? null, + ); + } +} diff --git a/src/JetStream/ExternalStream.php b/src/JetStream/ExternalStream.php new file mode 100644 index 00000000000..d996f7784d2 --- /dev/null +++ b/src/JetStream/ExternalStream.php @@ -0,0 +1,34 @@ + + */ + public function toArray(): array + { + $data = ['api' => $this->api]; + if ($this->deliver !== null) { + $data['deliver'] = $this->deliver; + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + api: $data['api'] ?? '', + deliver: $data['deliver'] ?? null, + ); + } +} diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 0e62eb8e2bd..4e827f5d765 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -136,9 +136,92 @@ public function getConsumerNames(string $stream): array return $data['consumers'] ?? []; } + /** + * List consumers of a stream as typed ConsumerInfo objects, following the + * API's offset/total paging until every consumer has been retrieved. + * + * @return list + */ + public function getConsumers(string $stream): array + { + $consumers = []; + $offset = 0; + + do { + $data = $this->apiRequest("CONSUMER.LIST.{$stream}", ['offset' => $offset]); + $page = $data['consumers'] ?? []; + foreach ($page as $entry) { + $consumers[] = ConsumerInfo::fromArray($entry); + } + $total = $data['total'] ?? \count($consumers); + $offset = \count($consumers); + } while ($page !== [] && $offset < $total); + + return $consumers; + } + + /** + * Create a push consumer and deliver its messages to the callback. + * + * If the config has no deliver subject, an inbox is generated. The callback + * receives a JetStreamMessage and may ack it. Drive delivery with + * Connection::wait(). + */ + public function pushSubscribe(string $stream, ConsumerConfig $config, \Closure $callback): PushSubscription + { + if ($config->deliverSubject === null) { + $config = ConsumerConfig::fromArray( + ['deliver_subject' => $this->conn->newInbox()] + $config->toArray(), + ); + } + + $consumer = $this->createConsumer($stream, $config); + + return new PushSubscription($this->conn, $consumer->info(), $callback); + } + + /** + * Create an ordered (ephemeral, in-order, auto-healing) push consumer. + */ + public function orderedConsumer(string $stream, DeliverPolicy $deliverPolicy = DeliverPolicy::All, ?string $filterSubject = null, float $idleHeartbeat = 5.0): OrderedConsumer + { + return new OrderedConsumer($this->conn, $this, $stream, $deliverPolicy, $filterSubject, $idleHeartbeat); + } + + // --- Stream Message Operations --- + + public function getMessage(string $stream, int $seq): StreamMessage + { + $data = $this->apiRequest("STREAM.MSG.GET.{$stream}", ['seq' => $seq]); + return StreamMessage::fromArray($data['message'] ?? []); + } + + public function getLastMessage(string $stream, string $subject): StreamMessage + { + $data = $this->apiRequest("STREAM.MSG.GET.{$stream}", ['last_by_subj' => $subject]); + return StreamMessage::fromArray($data['message'] ?? []); + } + + public function deleteMessage(string $stream, int $seq, bool $noErase = false): void + { + $payload = ['seq' => $seq]; + if ($noErase) { + $payload['no_erase'] = true; + } + $this->apiRequest("STREAM.MSG.DELETE.{$stream}", $payload); + } + // --- Publishing --- - public function publish(string $subject, string $data = '', ?Headers $headers = null, ?string $msgId = null, ?string $expectedLastMsgId = null, ?int $expectedLastSeq = null, ?int $expectedLastSubjectSeq = null, ?string $expectedStream = null): PubAck + /** + * @param int|string|null $ttl Per-message TTL (ADR-43). An int is interpreted as + * seconds; a string is passed through as a duration + * (e.g. "1s", "never"). Sets the Nats-TTL header. + * @param int $retryOnNoResponders Number of extra attempts if the publish is met + * with no responders / 503 (ADR-22). 0 keeps the + * default single-attempt behavior. + */ + public function publish(string $subject, string $data = '', ?Headers $headers = null, ?string $msgId = null, ?string $expectedLastMsgId = null, ?int $expectedLastSeq = null, ?int $expectedLastSubjectSeq = null, ?string $expectedStream = null, int|string|null $ttl = null, int $retryOnNoResponders = 0): PubAck { $headers ??= new Headers(); @@ -157,9 +240,25 @@ public function publish(string $subject, string $data = '', ?Headers $headers = if ($expectedStream !== null) { $headers->set('Nats-Expected-Stream', $expectedStream); } + if ($ttl !== null) { + $headers->set('Nats-TTL', \is_int($ttl) ? "{$ttl}s" : $ttl); + } $useHeaders = \count($headers) > 0 ? $headers : null; - $response = $this->conn->request($subject, $data, headers: $useHeaders); + + $attempt = 0; + while (true) { + try { + $response = $this->conn->request($subject, $data, headers: $useHeaders); + break; + } catch (\Utopia\NATS\Exception\NatsException $e) { + if ($attempt >= $retryOnNoResponders || $e->getMessage() !== 'No responders for request') { + throw $e; + } + $attempt++; + usleep(50_000 * $attempt); + } + } $responseData = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); self::checkError($responseData); @@ -188,11 +287,25 @@ public function deleteKeyValue(string $bucket): void $this->deleteStream("KV_{$bucket}"); } + // --- Object Store --- + + public function getObjectStore(string $bucket): \Utopia\NATS\ObjectStore\ObjectStore + { + // Verify the object-store stream exists. + $this->getStreamInfo("OBJ_{$bucket}"); + return new \Utopia\NATS\ObjectStore\ObjectStore($this->conn, $this, $bucket); + } + + public function deleteObjectStore(string $bucket): void + { + $this->deleteStream("OBJ_{$bucket}"); + } + // --- Account Info --- - public function accountInfo(): array + public function accountInfo(): AccountInfo { - return $this->apiRequest('INFO'); + return AccountInfo::fromArray($this->apiRequest('INFO')); } // --- Internal --- diff --git a/src/JetStream/JetStreamMessage.php b/src/JetStream/JetStreamMessage.php index 6baff6d0e35..6d82264e88a 100644 --- a/src/JetStream/JetStreamMessage.php +++ b/src/JetStream/JetStreamMessage.php @@ -22,6 +22,19 @@ public function ack(): void $this->respond(''); } + /** + * Acknowledge and wait for the server to confirm the ack was persisted. + * Unlike ack(), this blocks until confirmation or throws on timeout. + */ + public function ackSync(?float $timeout = null): void + { + if ($this->message->replyTo === null) { + throw new \RuntimeException('Cannot acknowledge: message has no reply subject'); + } + + $this->conn->request($this->message->replyTo, '', $timeout ?? 5.0); + } + public function nak(?float $delay = null): void { if ($delay !== null) { diff --git a/src/JetStream/OrderedConsumer.php b/src/JetStream/OrderedConsumer.php new file mode 100644 index 00000000000..244bc67b081 --- /dev/null +++ b/src/JetStream/OrderedConsumer.php @@ -0,0 +1,130 @@ +create(null); + } + + /** + * Return the next message in order, or null on timeout. Flow-control and + * heartbeat frames are handled internally; on a detected gap the consumer + * is recreated and iteration continues seamlessly. + */ + public function next(?float $timeout = null): ?JetStreamMessage + { + $timeout ??= 5.0; + $deadline = microtime(true) + $timeout; + + while (true) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + return null; + } + + $msg = $this->sub->nextMessage($remaining); + if (!$msg instanceof \Utopia\NATS\Message) { + return null; + } + + if (PushSubscription::handleControl($this->conn, $msg)) { + // A heartbeat reports the last consumer sequence the server has + // delivered; if it is ahead of us we missed messages. + $lastConsumer = $msg->headers?->get('Nats-Last-Consumer'); + if ($lastConsumer !== null && (int) $lastConsumer > $this->expectedConsumerSeq - 1) { + $this->reset($this->lastStreamSeq + 1); + } + continue; + } + + $jsMsg = new JetStreamMessage($this->conn, $msg); + $meta = $jsMsg->metadata(); + + if ($meta->consumerSequence !== $this->expectedConsumerSeq) { + // Gap: recreate from the message after the last good one. + $this->reset($this->lastStreamSeq + 1); + continue; + } + + $this->expectedConsumerSeq++; + $this->lastStreamSeq = $meta->streamSequence; + + return $jsMsg; + } + } + + public function getConsumerName(): string + { + return $this->info->name; + } + + public function stop(): void + { + $this->teardown(); + } + + private function reset(?int $startSeq): void + { + $this->teardown(); + $this->create($startSeq); + } + + private function teardown(): void + { + $this->sub->unsubscribe(); + try { + $this->js->deleteConsumer($this->stream, $this->info->name); + } catch (\Throwable) { + // Ephemeral consumer will expire on its own. + } + } + + private function create(?int $startSeq): void + { + // Subscribe before creating the consumer so no pushed message is missed. + $deliverSubject = $this->conn->newInbox(); + $this->sub = $this->conn->subscribe($deliverSubject); + + $config = new ConsumerConfig( + deliverPolicy: $startSeq !== null ? DeliverPolicy::ByStartSequence : $this->deliverPolicy, + ackPolicy: AckPolicy::None, + filterSubject: $this->filterSubject, + replayPolicy: ReplayPolicy::Instant, + inactiveThreshold: 30.0, + optStartSeq: $startSeq, + deliverSubject: $deliverSubject, + flowControl: true, + idleHeartbeat: $this->idleHeartbeat, + ); + + $this->info = $this->js->createConsumer($this->stream, $config)->info(); + $this->expectedConsumerSeq = 1; + } +} diff --git a/src/JetStream/Placement.php b/src/JetStream/Placement.php new file mode 100644 index 00000000000..103e336d882 --- /dev/null +++ b/src/JetStream/Placement.php @@ -0,0 +1,37 @@ +|null $tags + */ + public function __construct( + public readonly ?string $cluster = null, + public readonly ?array $tags = null, + ) {} + + public function toArray(): array + { + $data = []; + if ($this->cluster !== null) { + $data['cluster'] = $this->cluster; + } + if ($this->tags !== null) { + $data['tags'] = $this->tags; + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + cluster: $data['cluster'] ?? null, + tags: $data['tags'] ?? null, + ); + } +} diff --git a/src/JetStream/PushSubscription.php b/src/JetStream/PushSubscription.php new file mode 100644 index 00000000000..9c8fbcf7f9f --- /dev/null +++ b/src/JetStream/PushSubscription.php @@ -0,0 +1,88 @@ +config->deliverSubject; + if ($deliverSubject === null) { + throw new \RuntimeException('Consumer has no deliver subject; not a push consumer'); + } + + $handler = function (Message $msg) use ($callback): void { + if (self::handleControl($this->conn, $msg)) { + return; + } + $callback(new JetStreamMessage($this->conn, $msg)); + }; + + $this->sub = $this->conn->subscribe($deliverSubject, $handler, $info->config->deliverGroup); + } + + /** + * Handle a JetStream control (status 100) message. Returns true when the + * message was a control frame and should not be surfaced as data. + */ + public static function handleControl(Connection $conn, Message $msg): bool + { + if (!$msg->headers instanceof \Utopia\NATS\Headers) { + return false; + } + if ($msg->headers->getStatus() !== '100') { + return false; + } + + // Flow control request: acknowledge by responding to the reply subject. + if ($msg->replyTo !== null && $msg->replyTo !== '') { + $conn->publish($msg->replyTo, ''); + return true; + } + + // Idle heartbeat: may carry a stalled indicator to nudge flow control. + $stalled = $msg->headers->get('Nats-Consumer-Stalled'); + if ($stalled !== null && $stalled !== '') { + $conn->publish($stalled, ''); + } + + return true; + } + + public function getSubscription(): Subscription + { + return $this->sub; + } + + public function getConsumerInfo(): ConsumerInfo + { + return $this->info; + } + + public function getConsumerName(): string + { + return $this->info->name; + } + + public function unsubscribe(): void + { + $this->sub->unsubscribe(); + } +} diff --git a/src/JetStream/Republish.php b/src/JetStream/Republish.php new file mode 100644 index 00000000000..fbc07b7c617 --- /dev/null +++ b/src/JetStream/Republish.php @@ -0,0 +1,36 @@ + $this->source, + 'dest' => $this->destination, + ]; + if ($this->headersOnly) { + $data['headers_only'] = true; + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + source: $data['src'] ?? '', + destination: $data['dest'] ?? '', + headersOnly: $data['headers_only'] ?? false, + ); + } +} diff --git a/src/JetStream/SequenceInfo.php b/src/JetStream/SequenceInfo.php new file mode 100644 index 00000000000..d0969fb3dec --- /dev/null +++ b/src/JetStream/SequenceInfo.php @@ -0,0 +1,23 @@ + $subjects * @param float|null $maxAge Max age in seconds (converted to nanoseconds for wire) * @param float|null $duplicateWindow Duplicate window in seconds + * @param array|null $metadata Arbitrary key-value metadata (ADR-33) + * @param list|null $sources + * @param string|null $compression Compression algorithm: "none" or "s2" + * @param float|null $subjectDeleteMarkerTtl Delete-marker TTL in seconds (ADR-43) */ public function __construct( public readonly string $name, @@ -33,6 +37,17 @@ public function __construct( public readonly bool $denyDelete = false, public readonly bool $denyPurge = false, public readonly bool $allowRollup = false, + public readonly ?array $metadata = null, + public readonly ?StreamSource $mirror = null, + public readonly ?array $sources = null, + public readonly ?Republish $republish = null, + public readonly ?SubjectTransform $subjectTransform = null, + public readonly ?Placement $placement = null, + public readonly ?string $compression = null, + public readonly ?int $firstSeq = null, + public readonly ?ConsumerLimits $consumerLimits = null, + public readonly bool $allowMsgTtl = false, + public readonly ?float $subjectDeleteMarkerTtl = null, ) {} public function toArray(): array @@ -71,6 +86,42 @@ public function toArray(): array if ($this->duplicateWindow !== null) { $data['duplicate_window'] = self::secondsToNanos($this->duplicateWindow); } + if ($this->metadata !== null) { + $data['metadata'] = $this->metadata; + } + if ($this->mirror instanceof \Utopia\NATS\JetStream\StreamSource) { + $data['mirror'] = $this->mirror->toArray(); + } + if ($this->sources !== null) { + $data['sources'] = array_map( + static fn(StreamSource $s): array => $s->toArray(), + $this->sources, + ); + } + if ($this->republish instanceof \Utopia\NATS\JetStream\Republish) { + $data['republish'] = $this->republish->toArray(); + } + if ($this->subjectTransform instanceof \Utopia\NATS\JetStream\SubjectTransform) { + $data['subject_transform'] = $this->subjectTransform->toArray(); + } + if ($this->placement instanceof \Utopia\NATS\JetStream\Placement) { + $data['placement'] = $this->placement->toArray(); + } + if ($this->compression !== null) { + $data['compression'] = $this->compression; + } + if ($this->firstSeq !== null) { + $data['first_seq'] = $this->firstSeq; + } + if ($this->consumerLimits instanceof \Utopia\NATS\JetStream\ConsumerLimits) { + $data['consumer_limits'] = $this->consumerLimits->toArray(); + } + if ($this->allowMsgTtl) { + $data['allow_msg_ttl'] = true; + } + if ($this->subjectDeleteMarkerTtl !== null) { + $data['subject_delete_marker_ttl'] = self::secondsToNanos($this->subjectDeleteMarkerTtl); + } return $data; } @@ -99,6 +150,25 @@ public static function fromArray(array $data): self denyDelete: $data['deny_delete'] ?? false, denyPurge: $data['deny_purge'] ?? false, allowRollup: $data['allow_rollup_hdrs'] ?? false, + metadata: $data['metadata'] ?? null, + mirror: isset($data['mirror']) ? StreamSource::fromArray($data['mirror']) : null, + sources: isset($data['sources']) + ? array_map(StreamSource::fromArray(...), $data['sources']) + : null, + republish: isset($data['republish']) ? Republish::fromArray($data['republish']) : null, + subjectTransform: isset($data['subject_transform']) + ? SubjectTransform::fromArray($data['subject_transform']) + : null, + placement: isset($data['placement']) ? Placement::fromArray($data['placement']) : null, + compression: $data['compression'] ?? null, + firstSeq: $data['first_seq'] ?? null, + consumerLimits: isset($data['consumer_limits']) + ? ConsumerLimits::fromArray($data['consumer_limits']) + : null, + allowMsgTtl: $data['allow_msg_ttl'] ?? false, + subjectDeleteMarkerTtl: isset($data['subject_delete_marker_ttl']) + ? self::nanosToSeconds($data['subject_delete_marker_ttl']) + : null, ); } diff --git a/src/JetStream/StreamMessage.php b/src/JetStream/StreamMessage.php new file mode 100644 index 00000000000..600ff42458a --- /dev/null +++ b/src/JetStream/StreamMessage.php @@ -0,0 +1,43 @@ +|null $subjectTransforms + */ + public function __construct( + public readonly string $name, + public readonly ?int $optStartSeq = null, + public readonly ?string $optStartTime = null, + public readonly ?string $filterSubject = null, + public readonly ?array $subjectTransforms = null, + public readonly ?ExternalStream $external = null, + ) {} + + public function toArray(): array + { + $data = ['name' => $this->name]; + + if ($this->optStartSeq !== null) { + $data['opt_start_seq'] = $this->optStartSeq; + } + if ($this->optStartTime !== null) { + $data['opt_start_time'] = $this->optStartTime; + } + if ($this->filterSubject !== null) { + $data['filter_subject'] = $this->filterSubject; + } + if ($this->subjectTransforms !== null) { + $data['subject_transforms'] = array_map( + static fn(SubjectTransform $t): array => $t->toArray(), + $this->subjectTransforms, + ); + } + if ($this->external instanceof \Utopia\NATS\JetStream\ExternalStream) { + $data['external'] = $this->external->toArray(); + } + + return $data; + } + + public static function fromArray(array $data): self + { + return new self( + name: $data['name'] ?? '', + optStartSeq: $data['opt_start_seq'] ?? null, + optStartTime: $data['opt_start_time'] ?? null, + filterSubject: $data['filter_subject'] ?? null, + subjectTransforms: isset($data['subject_transforms']) + ? array_map(SubjectTransform::fromArray(...), $data['subject_transforms']) + : null, + external: isset($data['external']) ? ExternalStream::fromArray($data['external']) : null, + ); + } +} diff --git a/src/JetStream/StreamState.php b/src/JetStream/StreamState.php index 448defc86b1..2f51e48aa00 100644 --- a/src/JetStream/StreamState.php +++ b/src/JetStream/StreamState.php @@ -14,6 +14,8 @@ public function __construct( public readonly int $lastSeq, public readonly ?string $lastTs, public readonly int $consumerCount, + public readonly int $numDeleted = 0, + public readonly int $numSubjects = 0, ) {} public static function fromArray(array $data): self @@ -26,6 +28,8 @@ public static function fromArray(array $data): self lastSeq: $data['last_seq'] ?? 0, lastTs: $data['last_ts'] ?? null, consumerCount: $data['consumer_count'] ?? 0, + numDeleted: $data['num_deleted'] ?? 0, + numSubjects: $data['num_subjects'] ?? 0, ); } } diff --git a/src/JetStream/SubjectTransform.php b/src/JetStream/SubjectTransform.php new file mode 100644 index 00000000000..9901862bd77 --- /dev/null +++ b/src/JetStream/SubjectTransform.php @@ -0,0 +1,32 @@ + + */ + public function toArray(): array + { + return [ + 'src' => $this->source, + 'dest' => $this->destination, + ]; + } + + public static function fromArray(array $data): self + { + return new self( + source: $data['src'] ?? '', + destination: $data['dest'] ?? '', + ); + } +} diff --git a/src/KeyValue/KeyValue.php b/src/KeyValue/KeyValue.php index 3ec8708fe8c..51b86d4785c 100644 --- a/src/KeyValue/KeyValue.php +++ b/src/KeyValue/KeyValue.php @@ -7,7 +7,14 @@ use Utopia\NATS\Connection; use Utopia\NATS\Exception\KeyValueException; use Utopia\NATS\Headers; +use Utopia\NATS\JetStream\AckPolicy; +use Utopia\NATS\JetStream\ConsumerConfig; +use Utopia\NATS\JetStream\DeliverPolicy; use Utopia\NATS\JetStream\JetStream; +use Utopia\NATS\JetStream\JetStreamMessage; +use Utopia\NATS\JetStream\MsgMetadata; +use Utopia\NATS\Message; +use Utopia\NATS\Subscription; final class KeyValue { @@ -181,11 +188,303 @@ public function status(): KeyValueStatus ); } + /** + * Fetch a specific revision of a key by its stream sequence. + */ + public function getRevision(string $key, int $seq): KeyValueEntry + { + $this->validateKey($key); + + return $this->fetchStored(['seq' => $seq], $key); + } + + /** + * Return every stored revision for a key, oldest first. + * + * @return list + */ + public function history(string $key): array + { + $this->validateKey($key); + + $stream = "KV_{$this->bucket}"; + $subject = "\$KV.{$this->bucket}.{$key}"; + + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + deliverPolicy: DeliverPolicy::All, + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + inactiveThreshold: 30.0, + )); + + try { + $entries = []; + foreach ($consumer->fetch(1024, 1.0) as $msg) { + $entries[] = $this->entryFromDelivered($key, $msg); + $msg->ack(); + } + + return $entries; + } finally { + try { + $this->js->deleteConsumer($stream, $consumer->getName()); + } catch (\Throwable) { + // Ephemeral consumer will expire on its own. + } + } + } + + /** + * Watch a key (or wildcard pattern) for updates. + * + * The returned subscription must be pumped by the connection + * (e.g. `$conn->wait()`), and unsubscribed when no longer needed. + * + * With no options, only new updates are delivered (the historical + * default). Options change what is delivered: + * - includeHistory: every stored revision is delivered first, then live updates. + * - updatesOnly: only updates from now on (equivalent to the default). + * - ignoreDeletes: DEL/PURGE marker entries are not passed to `$callback`. + * - metaOnly: entries carry headers/metadata only, with an empty value. + * + * When `$onInitDone` is provided it is invoked exactly once, right after the + * initial/historical set has been fully delivered and before any live update. + * If there is no history to replay it fires immediately. Every `$callback` + * invocation after `$onInitDone` is a live update. + * + * @param callable(KeyValueEntry): void $callback + * @param callable(): void|null $onInitDone + */ + public function watch( + string $keyPattern, + callable $callback, + ?KeyValueWatchOptions $options = null, + ?callable $onInitDone = null, + ): Subscription { + $options ??= new KeyValueWatchOptions(updatesOnly: true); + + $stream = "KV_{$this->bucket}"; + $filter = "\$KV.{$this->bucket}.{$keyPattern}"; + $deliverSubject = $this->conn->newInbox(); + + $deliverPolicy = match (true) { + $options->includeHistory => DeliverPolicy::All->value, + $options->updatesOnly => DeliverPolicy::New->value, + default => DeliverPolicy::LastPerSubject->value, + }; + + $config = [ + 'deliver_subject' => $deliverSubject, + 'deliver_policy' => $deliverPolicy, + 'ack_policy' => 'none', + 'filter_subject' => $filter, + 'inactive_threshold' => 30 * 1_000_000_000, + ]; + if ($options->metaOnly) { + $config['headers_only'] = true; + } + + $response = $this->conn->request("\$JS.API.CONSUMER.CREATE.{$stream}", json_encode([ + 'stream_name' => $stream, + 'config' => $config, + ], JSON_THROW_ON_ERROR)); + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($data); + + $numPending = (int) ($data['num_pending'] ?? 0); + $delivered = 0; + $initSignaled = false; + + $sub = $this->conn->subscribe($deliverSubject, function (Message $msg) use ($callback, $options, $onInitDone, $numPending, &$delivered, &$initSignaled): void { + // Ignore JetStream idle heartbeats / flow control (100 status). + if ($msg->headers instanceof Headers && $msg->headers->getStatus() !== '') { + return; + } + + $delivered++; + $entry = $this->entryFromMessage($msg); + + $isMarker = $entry->operation === KeyValueOperation::Delete + || $entry->operation === KeyValueOperation::Purge; + if (!$options->ignoreDeletes || !$isMarker) { + $callback($entry); + } + + if (!$initSignaled && $delivered >= $numPending) { + $initSignaled = true; + if ($onInitDone !== null) { + $onInitDone(); + } + } + }); + + if ($numPending === 0 && !$initSignaled) { + $initSignaled = true; + if ($onInitDone !== null) { + $onInitDone(); + } + } + + return $sub; + } + + /** + * Remove DEL/PURGE tombstone markers: for every key whose latest entry is a + * delete/purge marker, purge that subject. + * + * @param float|null $threshold When set, only markers older than this many + * seconds are removed; newer markers are kept. + * Null removes all delete markers. + * @return int Number of keys whose tombstones were removed. + */ + public function purgeDeletes(?float $threshold = null): int + { + $purged = 0; + $now = microtime(true); + + foreach ($this->keys() as $key) { + $subject = "\$KV.{$this->bucket}.{$key}"; + + try { + $msg = $this->conn->request("\$JS.API.DIRECT.GET.KV_{$this->bucket}", json_encode([ + 'last_by_subj' => $subject, + ], JSON_THROW_ON_ERROR)); + } catch (\Throwable) { + continue; + } + + if (!$msg->headers instanceof Headers) { + continue; + } + + $op = $msg->headers->get('KV-Operation'); + if ($op !== 'DEL' && $op !== 'PURGE') { + continue; + } + + // keep=1 retains a not-yet-expired marker; keep=0 removes the subject entirely. + $keep = 0; + if ($threshold !== null) { + $ts = $msg->headers->get('Nats-Time-Stamp'); + $created = $ts !== null ? strtotime($ts) : false; + if ($created !== false && ($now - $created) < $threshold) { + $keep = 1; + } + } + + $response = $this->conn->request("\$JS.API.STREAM.PURGE.KV_{$this->bucket}", json_encode([ + 'filter' => $subject, + 'keep' => $keep, + ], JSON_THROW_ON_ERROR)); + $result = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($result); + + if ($keep === 0) { + $purged++; + } + } + + return $purged; + } + public function getBucket(): string { return $this->bucket; } + private function entryFromDelivered(string $key, JetStreamMessage $msg): KeyValueEntry + { + return new KeyValueEntry( + bucket: $this->bucket, + key: $key, + value: $msg->getData(), + revision: $msg->metadata()->streamSequence, + created: $msg->metadata()->timestamp, + operation: $this->operationFromHeaders($msg->getHeaders()), + ); + } + + private function entryFromMessage(Message $msg): KeyValueEntry + { + $prefix = "\$KV.{$this->bucket}."; + $key = str_starts_with($msg->subject, $prefix) + ? substr($msg->subject, \strlen($prefix)) + : $msg->subject; + + $revision = 0; + $created = null; + if ($msg->replyTo !== null) { + $meta = MsgMetadata::fromReplySubject($msg->replyTo); + $revision = $meta->streamSequence; + $created = $meta->timestamp; + } + + return new KeyValueEntry( + bucket: $this->bucket, + key: $key, + value: $msg->data, + revision: $revision, + created: $created, + operation: $this->operationFromHeaders($msg->headers), + ); + } + + /** + * @param array $request + */ + private function fetchStored(array $request, string $key): KeyValueEntry + { + try { + $response = $this->conn->request( + "\$JS.API.STREAM.MSG.GET.KV_{$this->bucket}", + json_encode($request, JSON_THROW_ON_ERROR), + ); + } catch (\Throwable $e) { + throw new KeyValueException("Revision not found for key: {$key}", $e->getCode(), previous: $e); + } + + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($data); + + $stored = $data['message'] ?? null; + if (!\is_array($stored)) { + throw new KeyValueException("Revision not found for key: {$key}"); + } + + // A sequence lookup is stream-global: reject a revision that belongs to a different key. + $expectedSubject = "\$KV.{$this->bucket}.{$key}"; + if (isset($stored['subject']) && $stored['subject'] !== $expectedSubject) { + throw new KeyValueException("Revision not found for key: {$key}"); + } + + $headers = null; + if (isset($stored['hdrs']) && \is_string($stored['hdrs'])) { + $headers = Headers::fromWire((string) base64_decode($stored['hdrs'], true)); + } + + return new KeyValueEntry( + bucket: $this->bucket, + key: $key, + value: isset($stored['data']) ? (string) base64_decode((string) $stored['data'], true) : '', + revision: (int) ($stored['seq'] ?? 0), + created: $stored['time'] ?? null, + operation: $this->operationFromHeaders($headers), + ); + } + + private function operationFromHeaders(?Headers $headers): KeyValueOperation + { + if (!$headers instanceof Headers) { + return KeyValueOperation::Put; + } + + return match ($headers->get('KV-Operation')) { + 'DEL' => KeyValueOperation::Delete, + 'PURGE' => KeyValueOperation::Purge, + default => KeyValueOperation::Put, + }; + } + private function validateKey(string $key): void { if ($key === '' || str_contains($key, ' ') || str_contains($key, '>') || str_contains($key, '*')) { diff --git a/src/KeyValue/KeyValueConfig.php b/src/KeyValue/KeyValueConfig.php index 7418853b774..68c4d737275 100644 --- a/src/KeyValue/KeyValueConfig.php +++ b/src/KeyValue/KeyValueConfig.php @@ -40,7 +40,6 @@ public function toStreamConfig(): StreamConfig replicas: $this->replicas, discard: DiscardPolicy::New, allowDirect: true, - mirrorDirect: true, allowRollup: true, ); } diff --git a/src/KeyValue/KeyValueWatchOptions.php b/src/KeyValue/KeyValueWatchOptions.php new file mode 100644 index 00000000000..cbcf41ebe51 --- /dev/null +++ b/src/KeyValue/KeyValueWatchOptions.php @@ -0,0 +1,25 @@ + + */ + public function toArray(): array + { + $data = ['bucket' => $this->bucket]; + if ($this->name !== null && $this->name !== '') { + $data['name'] = $this->name; + } + + return $data; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + return new self( + bucket: (string) ($data['bucket'] ?? ''), + name: isset($data['name']) ? (string) $data['name'] : null, + ); + } +} diff --git a/src/ObjectStore/ObjectMeta.php b/src/ObjectStore/ObjectMeta.php new file mode 100644 index 00000000000..653d3fc2db1 --- /dev/null +++ b/src/ObjectStore/ObjectMeta.php @@ -0,0 +1,88 @@ +|null $metadata + */ + public function __construct( + public readonly string $name, + public readonly string $bucket, + public readonly string $nuid, + public readonly int $size, + public readonly int $chunks, + public readonly string $digest, + public readonly ?string $description = null, + public readonly ?string $modified = null, + public readonly bool $deleted = false, + public readonly ?array $metadata = null, + public readonly ?ObjectLink $link = null, + ) {} + + /** + * @return array + */ + public function toArray(): array + { + $data = [ + 'name' => $this->name, + 'bucket' => $this->bucket, + 'nuid' => $this->nuid, + 'size' => $this->size, + 'chunks' => $this->chunks, + 'digest' => $this->digest, + ]; + + if ($this->description !== null) { + $data['description'] = $this->description; + } + if ($this->modified !== null) { + $data['mtime'] = $this->modified; + } + if ($this->deleted) { + $data['deleted'] = true; + } + if ($this->metadata !== null && $this->metadata !== []) { + $data['metadata'] = $this->metadata; + } + if ($this->link instanceof ObjectLink) { + $data['options'] = ['link' => $this->link->toArray()]; + } + + return $data; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + $metadata = null; + if (isset($data['metadata']) && \is_array($data['metadata'])) { + $metadata = array_map(static fn(mixed $v): string => (string) $v, $data['metadata']); + } + + $link = null; + if (isset($data['options']['link']) && \is_array($data['options']['link'])) { + $link = ObjectLink::fromArray($data['options']['link']); + } + + return new self( + name: (string) ($data['name'] ?? ''), + bucket: (string) ($data['bucket'] ?? ''), + nuid: (string) ($data['nuid'] ?? ''), + size: (int) ($data['size'] ?? 0), + chunks: (int) ($data['chunks'] ?? 0), + digest: (string) ($data['digest'] ?? ''), + description: isset($data['description']) ? (string) $data['description'] : null, + modified: isset($data['mtime']) ? (string) $data['mtime'] : null, + deleted: (bool) ($data['deleted'] ?? false), + metadata: $metadata, + link: $link, + ); + } +} diff --git a/src/ObjectStore/ObjectStore.php b/src/ObjectStore/ObjectStore.php new file mode 100644 index 00000000000..c3e338bc57a --- /dev/null +++ b/src/ObjectStore/ObjectStore.php @@ -0,0 +1,491 @@ +createOrUpdateStream($config->toStreamConfig()); + + return new self($conn, $js, $config->bucket); + } + + /** + * Store an object, chunking its data and writing a meta record. + */ + public function put(string $name, string $data): ObjectMeta + { + [$previous, $previousSeq] = $this->readMetaWithSeq($name); + + return $this->writeVersion($name, $data, $previous, $previousSeq); + } + + /** + * Write a new version of an object, expecting the meta subject to still be at + * $expectedSeq (0 = must not exist yet). Separated from put() so the optimistic + * concurrency path can be driven deterministically in tests. + */ + private function writeVersion(string $name, string $data, ?ObjectMeta $previous, int $expectedSeq): ObjectMeta + { + $nuid = strtoupper(bin2hex(random_bytes(12))); + $chunkSubject = "\$O.{$this->bucket}.C.{$nuid}"; + + $chunks = 0; + $length = \strlen($data); + for ($offset = 0; $offset < $length; $offset += self::CHUNK_SIZE) { + $this->js->publish($chunkSubject, substr($data, $offset, self::CHUNK_SIZE)); + $chunks++; + } + + $meta = new ObjectMeta( + name: $name, + bucket: $this->bucket, + nuid: $nuid, + size: $length, + chunks: $chunks, + digest: $this->digest($data), + modified: gmdate('Y-m-d\TH:i:s\Z'), + ); + + // Optimistic concurrency: the meta publish only succeeds if the meta subject's + // last sequence still matches what we read (0 means "must not exist yet"). If a + // concurrent or stale writer already advanced it, JetStream rejects the publish, + // we purge only the chunks THIS put wrote (never the previous NUID), and surface a + // clear conflict. This replaces the former last-writer-wins behaviour that could + // orphan the loser's chunks. + $headers = new Headers(); + $headers->set('Nats-Rollup', 'sub'); + try { + $this->js->publish( + $this->metaSubject($name), + json_encode($meta->toArray(), JSON_THROW_ON_ERROR), + $headers, + expectedLastSubjectSeq: $expectedSeq, + ); + } catch (\Throwable $e) { + $this->purgeChunks($nuid); + throw new ObjectStoreException("conflicting concurrent write for object: {$name}", $e->getCode(), previous: $e); + } + + // Reclaim chunks left behind by a prior version of this object. + if ($previous instanceof ObjectMeta && $previous->nuid !== '' && $previous->nuid !== $nuid) { + $this->purgeChunks($previous->nuid); + } + + return $meta; + } + + /** + * Retrieve an object's bytes, reassembling and verifying its chunks. + */ + public function get(string $name): string + { + $meta = $this->readMeta($name); + if (!$meta instanceof ObjectMeta || $meta->deleted) { + throw new \RuntimeException("Object not found: {$name}"); + } + + // A link transparently resolves to its target's bytes (same bucket). A + // bucket link has no target object, so it cannot be read as an object. + if ($meta->link instanceof ObjectLink) { + if ($meta->link->name === null || $meta->link->name === '') { + throw new ObjectStoreException("cannot get a bucket link: {$name}"); + } + + return $this->get($meta->link->name); + } + + $data = ''; + if ($meta->chunks > 0) { + $stream = "OBJ_{$this->bucket}"; + $subject = "\$O.{$this->bucket}.C.{$meta->nuid}"; + + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + deliverPolicy: DeliverPolicy::All, + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + inactiveThreshold: 30.0, + )); + + try { + foreach ($consumer->fetch($meta->chunks, 10.0) as $msg) { + $data .= $msg->getData(); + $msg->ack(); + } + } finally { + try { + $this->js->deleteConsumer($stream, $consumer->getName()); + } catch (\Throwable) { + // Ephemeral consumer expires on its own. + } + } + } + + if (\strlen($data) !== $meta->size || $this->digest($data) !== $meta->digest) { + throw new \RuntimeException("Object integrity check failed: {$name}"); + } + + return $data; + } + + public function getMeta(string $name): ObjectMeta + { + $meta = $this->readMeta($name); + if (!$meta instanceof ObjectMeta || $meta->deleted) { + throw new \RuntimeException("Object not found: {$name}"); + } + + return $meta; + } + + public function delete(string $name): void + { + [$meta, $expectedSeq] = $this->readMetaWithSeq($name); + if (!$meta instanceof ObjectMeta || $meta->deleted) { + return; + } + + $this->deleteVersion($meta, $expectedSeq); + } + + /** + * Commit a deletion tombstone guarded by optimistic concurrency, then reclaim the + * object's chunks. The guarded publish goes FIRST: a concurrent put()/updateMeta() + * advances the meta subject, so this conflicts and throws before any chunk is purged, + * leaving the replacement version intact. Separated so the conflict path is testable. + */ + private function deleteVersion(ObjectMeta $meta, int $expectedSeq): void + { + // Write a deletion marker (tombstone) rather than purging the meta subject, + // so watchers observe the delete and get()/list() still treat it as gone. + // The rollup header collapses the subject to this single record. + $tombstone = new ObjectMeta( + name: $meta->name, + bucket: $meta->bucket, + nuid: '', + size: 0, + chunks: 0, + digest: '', + description: $meta->description, + modified: gmdate('Y-m-d\TH:i:s\Z'), + deleted: true, + ); + + $this->publishMeta($tombstone, $expectedSeq); + + // Only after the tombstone is committed do we reclaim the chunks. + if ($meta->nuid !== '') { + $this->purgeChunks($meta->nuid); + } + } + + /** + * List all (non-deleted) objects in the bucket. + * + * @return list + */ + public function list(): array + { + $stream = "OBJ_{$this->bucket}"; + $subject = "\$O.{$this->bucket}.M.>"; + + try { + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + deliverPolicy: DeliverPolicy::LastPerSubject, + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + inactiveThreshold: 30.0, + )); + } catch (\Throwable) { + return []; + } + + try { + $objects = []; + foreach ($consumer->fetch(1024, 1.0) as $msg) { + $msg->ack(); + $decoded = json_decode((string) $msg->getData(), true, 512, JSON_THROW_ON_ERROR); + if (!\is_array($decoded)) { + continue; + } + $meta = ObjectMeta::fromArray($decoded); + if (!$meta->deleted) { + $objects[] = $meta; + } + } + + return $objects; + } finally { + try { + $this->js->deleteConsumer($stream, $consumer->getName()); + } catch (\Throwable) { + // Ephemeral consumer expires on its own. + } + } + } + + public function status(): StreamInfo + { + return $this->js->getStreamInfo("OBJ_{$this->bucket}"); + } + + /** + * Watch the bucket for object meta updates (puts and deletes), delivering an + * ObjectMeta to the callback for each change. + * + * Backed by an ephemeral push consumer on the meta subject created via the raw + * JetStream API. The returned subscription must be pumped by the connection + * (e.g. `$conn->wait()` / `$conn->processMessage()`) and unsubscribed when done. + * + * @param callable(ObjectMeta): void $callback + * @param bool $includeHistory deliver the current meta of every object first + */ + public function watch(callable $callback, bool $includeHistory = false): Subscription + { + $stream = "OBJ_{$this->bucket}"; + $filter = "\$O.{$this->bucket}.M.>"; + $deliverSubject = $this->conn->newInbox(); + + $payload = json_encode([ + 'stream_name' => $stream, + 'config' => [ + 'deliver_subject' => $deliverSubject, + 'deliver_policy' => $includeHistory ? 'last_per_subject' : 'new', + 'ack_policy' => 'none', + 'filter_subject' => $filter, + 'inactive_threshold' => 30 * 1_000_000_000, + ], + ], JSON_THROW_ON_ERROR); + + $response = $this->conn->request("\$JS.API.CONSUMER.CREATE.{$stream}", $payload); + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($data); + + return $this->conn->subscribe($deliverSubject, function (Message $msg) use ($callback): void { + // Ignore JetStream idle heartbeats / flow control (status messages). + if ($msg->headers instanceof Headers && $msg->headers->getStatus() !== '') { + return; + } + + $decoded = json_decode($msg->data, true, 512, JSON_THROW_ON_ERROR); + if (!\is_array($decoded)) { + return; + } + + $callback(ObjectMeta::fromArray($decoded)); + }); + } + + /** + * Create a link under $linkName pointing to another object in this bucket. + * get($linkName) then transparently resolves to the target's bytes. + */ + public function addLink(string $linkName, string $targetObjectName): ObjectMeta + { + return $this->writeLink($linkName, new ObjectLink($this->bucket, $targetObjectName)); + } + + /** + * Create a link under $linkName pointing to an entire bucket. A bucket link + * cannot be read via get(); doing so throws. + */ + public function addBucketLink(string $linkName, string $targetBucket): ObjectMeta + { + return $this->writeLink($linkName, new ObjectLink($targetBucket)); + } + + /** + * Update mutable meta fields, preserving the stored bytes (nuid/size/chunks/digest). + * + * @param array|null $metadata + */ + public function updateMeta(string $name, ?string $description = null, ?array $metadata = null): ObjectMeta + { + [$previous, $expectedSeq] = $this->readMetaWithSeq($name); + if (!$previous instanceof ObjectMeta || $previous->deleted) { + throw new \RuntimeException("Object not found: {$name}"); + } + + $meta = new ObjectMeta( + name: $previous->name, + bucket: $previous->bucket, + nuid: $previous->nuid, + size: $previous->size, + chunks: $previous->chunks, + digest: $previous->digest, + description: $description ?? $previous->description, + modified: gmdate('Y-m-d\TH:i:s\Z'), + metadata: $metadata ?? $previous->metadata, + link: $previous->link, + ); + + $this->publishMeta($meta, $expectedSeq); + + return $meta; + } + + /** + * Seal the bucket, making it permanently read-only by updating the backing + * stream config. After sealing, the server rejects put()/delete(). + */ + public function seal(): void + { + $stream = "OBJ_{$this->bucket}"; + + $infoResponse = $this->conn->request("\$JS.API.STREAM.INFO.{$stream}"); + $info = json_decode($infoResponse->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($info); + + $config = $info['config'] ?? []; + if (!\is_array($config)) { + throw new ObjectStoreException("cannot read stream config for bucket: {$this->bucket}"); + } + // Empty JSON objects in the info response (e.g. consumer_limits) decode to + // empty PHP arrays and would re-encode as [] — which the API rejects as + // invalid JSON for a struct field. Drop them; the server re-applies defaults. + $config = array_filter($config, static fn(mixed $v): bool => $v !== []); + $config['sealed'] = true; + + $updateResponse = $this->conn->request( + "\$JS.API.STREAM.UPDATE.{$stream}", + json_encode($config, JSON_THROW_ON_ERROR), + ); + $updated = json_decode($updateResponse->data, true, 512, JSON_THROW_ON_ERROR); + JetStream::checkError($updated); + } + + public function getBucket(): string + { + return $this->bucket; + } + + private function writeLink(string $linkName, ObjectLink $link): ObjectMeta + { + [$previous, $expectedSeq] = $this->readMetaWithSeq($linkName); + + $meta = new ObjectMeta( + name: $linkName, + bucket: $this->bucket, + nuid: '', + size: 0, + chunks: 0, + digest: '', + modified: gmdate('Y-m-d\TH:i:s\Z'), + link: $link, + ); + + $this->publishMeta($meta, $expectedSeq); + + // Reclaim chunks left behind if this name previously held a real object. + if ($previous instanceof ObjectMeta && $previous->nuid !== '') { + $this->purgeChunks($previous->nuid); + } + + return $meta; + } + + /** + * Publish a meta record with a rolling-up header under the optimistic-concurrency + * guard: the publish only succeeds if the meta subject is still at $expectedSeq. + */ + private function publishMeta(ObjectMeta $meta, int $expectedSeq): void + { + $headers = new Headers(); + $headers->set('Nats-Rollup', 'sub'); + + try { + $this->js->publish( + $this->metaSubject($meta->name), + json_encode($meta->toArray(), JSON_THROW_ON_ERROR), + $headers, + expectedLastSubjectSeq: $expectedSeq, + ); + } catch (\Throwable $e) { + throw new ObjectStoreException("conflicting concurrent write for object: {$meta->name}", $e->getCode(), previous: $e); + } + } + + private function readMeta(string $name): ?ObjectMeta + { + return $this->readMetaWithSeq($name)[0]; + } + + /** + * Read the latest meta record for an object along with its stream sequence. + * The sequence is 0 when no meta record exists, which callers use as the + * expected-last-subject-sequence for an optimistic first write. + * + * @return array{0: ?ObjectMeta, 1: int} + */ + private function readMetaWithSeq(string $name): array + { + try { + $response = $this->conn->request( + "\$JS.API.STREAM.MSG.GET.OBJ_{$this->bucket}", + json_encode(['last_by_subj' => $this->metaSubject($name)], JSON_THROW_ON_ERROR), + ); + } catch (\Throwable) { + return [null, 0]; + } + + $data = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + if (isset($data['error']) || !isset($data['message']['data'])) { + return [null, 0]; + } + + $decoded = json_decode((string) base64_decode((string) $data['message']['data'], true), true, 512, JSON_THROW_ON_ERROR); + if (!\is_array($decoded)) { + return [null, 0]; + } + + return [ObjectMeta::fromArray($decoded), (int) ($data['message']['seq'] ?? 0)]; + } + + private function purgeChunks(string $nuid): void + { + try { + $this->js->purgeStream("OBJ_{$this->bucket}", "\$O.{$this->bucket}.C.{$nuid}"); + } catch (\Throwable) { + // Best effort cleanup. + } + } + + private function metaSubject(string $name): string + { + $token = rtrim(strtr(base64_encode($name), '+/', '-_'), '='); + + return "\$O.{$this->bucket}.M.{$token}"; + } + + private function digest(string $data): string + { + $raw = hash('sha256', $data, true); + + return 'SHA-256=' . rtrim(strtr(base64_encode($raw), '+/', '-_'), '='); + } +} diff --git a/src/ObjectStore/ObjectStoreConfig.php b/src/ObjectStore/ObjectStoreConfig.php new file mode 100644 index 00000000000..aad1747b7c3 --- /dev/null +++ b/src/ObjectStore/ObjectStoreConfig.php @@ -0,0 +1,45 @@ +bucket}", + subjects: [ + "\$O.{$this->bucket}.C.>", + "\$O.{$this->bucket}.M.>", + ], + description: $this->description, + retention: RetentionPolicy::Limits, + maxBytes: $this->maxBytes, + maxAge: $this->ttl, + storage: $this->storage, + replicas: $this->replicas, + discard: DiscardPolicy::New, + allowDirect: true, + allowRollup: true, + ); + } +} diff --git a/src/Services/Endpoint.php b/src/Services/Endpoint.php new file mode 100644 index 00000000000..dd134063bc4 --- /dev/null +++ b/src/Services/Endpoint.php @@ -0,0 +1,68 @@ + $metadata + */ + public function __construct( + public readonly string $name, + public readonly string $subject, + callable $handler, + public readonly string $queueGroup = 'q', + public readonly array $metadata = [], + ) { + $this->handler = $handler; + } + + /** + * @return array + */ + public function info(): array + { + return [ + 'name' => $this->name, + 'subject' => $this->subject, + 'queue_group' => $this->queueGroup, + 'metadata' => $this->metadata === [] ? new \stdClass() : $this->metadata, + ]; + } + + /** + * @return array + */ + public function stats(): array + { + $average = $this->numRequests > 0 + ? intdiv($this->processingTime, $this->numRequests) + : 0; + + return [ + 'name' => $this->name, + 'subject' => $this->subject, + 'queue_group' => $this->queueGroup, + 'metadata' => $this->metadata === [] ? new \stdClass() : $this->metadata, + 'num_requests' => $this->numRequests, + 'num_errors' => $this->numErrors, + 'last_error' => $this->lastError ?? '', + 'processing_time' => $this->processingTime, + 'average_processing_time' => $average, + ]; + } +} diff --git a/src/Services/Group.php b/src/Services/Group.php new file mode 100644 index 00000000000..82467df3bad --- /dev/null +++ b/src/Services/Group.php @@ -0,0 +1,59 @@ +service, + $this->prefix . '.' . $name, + $queueGroup ?? $this->queueGroup, + ); + } + + /** + * Register an endpoint under this group. The subject is prefixed with + * the group name; when $subject is omitted the endpoint name is used. + * + * @param callable(Message): string $handler + * @param array $metadata + */ + public function addEndpoint( + string $name, + callable $handler, + ?string $subject = null, + ?string $queueGroup = null, + array $metadata = [], + ): self { + $this->service->registerEndpoint( + $name, + $this->prefix . '.' . ($subject ?? $name), + $handler, + $queueGroup ?? $this->queueGroup, + $metadata, + ); + + return $this; + } +} diff --git a/src/Services/Service.php b/src/Services/Service.php new file mode 100644 index 00000000000..24bf8a9f458 --- /dev/null +++ b/src/Services/Service.php @@ -0,0 +1,261 @@ + */ + private array $endpoints = []; + + /** @var list */ + private array $subscriptions = []; + + private bool $running = false; + + /** + * @param array $metadata + */ + public function __construct( + private readonly Connection $conn, + private readonly string $name, + private readonly string $version, + private readonly string $description = '', + private readonly array $metadata = [], + ) { + $this->id = strtoupper(bin2hex(random_bytes(11))); + $this->started = gmdate('Y-m-d\TH:i:s\Z'); + } + + /** + * Register an endpoint handler. The handler receives the request + * Message and returns the reply payload as a string. + * + * @param callable(Message): string $handler + * @param array $metadata + */ + public function addEndpoint( + string $name, + string $subject, + callable $handler, + ?string $queueGroup = null, + array $metadata = [], + ): self { + $this->registerEndpoint($name, $subject, $handler, $queueGroup, $metadata); + + return $this; + } + + /** + * Create an endpoint group with a subject prefix and optional default + * queue group inherited by its endpoints and nested groups. + */ + public function addGroup(string $name, ?string $queueGroup = null): Group + { + return new Group($this, $name, $queueGroup); + } + + /** + * Register an endpoint on the service. Used by both the bare + * addEndpoint and the group handles. + * + * @param callable(Message): string $handler + * @param array $metadata + * + * @internal + */ + public function registerEndpoint( + string $name, + string $subject, + callable $handler, + ?string $queueGroup = null, + array $metadata = [], + ): void { + $this->endpoints[$name] = new Endpoint($name, $subject, $handler, $queueGroup ?? 'q', $metadata); + + if ($this->running) { + $this->subscribeEndpoint($this->endpoints[$name]); + } + } + + /** + * Subscribe all endpoint handlers and discovery responders. + * Non-blocking: the caller must pump the connection. + */ + public function start(): self + { + if ($this->running) { + return $this; + } + $this->running = true; + + foreach ($this->endpoints as $endpoint) { + $this->subscribeEndpoint($endpoint); + } + + $this->subscribeDiscovery('PING', fn(Message $msg) => $this->reply($msg, $this->pingResponse())); + $this->subscribeDiscovery('INFO', fn(Message $msg) => $this->reply($msg, $this->infoResponse())); + $this->subscribeDiscovery('STATS', fn(Message $msg) => $this->reply($msg, $this->statsResponse())); + + return $this; + } + + /** + * Start and then block, processing messages until stopped. + */ + public function run(): void + { + $this->start(); + $this->conn->wait(); + } + + public function stop(): void + { + foreach ($this->subscriptions as $sub) { + try { + $sub->unsubscribe(); + } catch (\Throwable) { + // ignore + } + } + $this->subscriptions = []; + $this->running = false; + } + + public function getId(): string + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + private function subscribeEndpoint(Endpoint $endpoint): void + { + $this->subscriptions[] = $this->conn->subscribe( + $endpoint->subject, + function (Message $msg) use ($endpoint): void { + $this->handleEndpoint($endpoint, $msg); + }, + $endpoint->queueGroup, + ); + } + + private function handleEndpoint(Endpoint $endpoint, Message $msg): void + { + $endpoint->numRequests++; + $start = hrtime(true); + + try { + $result = ($endpoint->handler)($msg); + $endpoint->processingTime += (int) (hrtime(true) - $start); + + if ($msg->replyTo !== null) { + $this->conn->publish($msg->replyTo, $result); + } + } catch (\Throwable $e) { + $endpoint->processingTime += (int) (hrtime(true) - $start); + $endpoint->numErrors++; + $endpoint->lastError = $e->getMessage(); + + if ($msg->replyTo !== null) { + $code = $e instanceof ServiceException ? $e->getErrorCode() : '500'; + $headers = new Headers(); + $headers->set('Nats-Service-Error', $e->getMessage()); + $headers->set('Nats-Service-Error-Code', $code); + $this->conn->publish($msg->replyTo, '', headers: $headers); + } + } + } + + private function subscribeDiscovery(string $verb, \Closure $callback): void + { + foreach ([ + self::API_PREFIX . ".{$verb}", + self::API_PREFIX . ".{$verb}.{$this->name}", + self::API_PREFIX . ".{$verb}.{$this->name}.{$this->id}", + ] as $subject) { + $this->subscriptions[] = $this->conn->subscribe($subject, $callback); + } + } + + private function reply(Message $msg, string $payload): void + { + if ($msg->replyTo !== null) { + $this->conn->publish($msg->replyTo, $payload); + } + } + + private function pingResponse(): string + { + return $this->encode([ + 'type' => self::TYPE_PREFIX . '.ping_response', + 'name' => $this->name, + 'id' => $this->id, + 'version' => $this->version, + 'metadata' => $this->metadataObject(), + ]); + } + + private function infoResponse(): string + { + return $this->encode([ + 'type' => self::TYPE_PREFIX . '.info_response', + 'name' => $this->name, + 'id' => $this->id, + 'version' => $this->version, + 'description' => $this->description, + 'metadata' => $this->metadataObject(), + 'endpoints' => array_map(fn(Endpoint $e): array => $e->info(), array_values($this->endpoints)), + ]); + } + + private function statsResponse(): string + { + return $this->encode([ + 'type' => self::TYPE_PREFIX . '.stats_response', + 'name' => $this->name, + 'id' => $this->id, + 'version' => $this->version, + 'started' => $this->started, + 'metadata' => $this->metadataObject(), + 'endpoints' => array_map(fn(Endpoint $e): array => $e->stats(), array_values($this->endpoints)), + ]); + } + + /** + * Encode service metadata as a JSON object (empty stays {} not []). + * + * @return array|\stdClass + */ + private function metadataObject(): array|\stdClass + { + return $this->metadata === [] ? new \stdClass() : $this->metadata; + } + + /** + * @param array $data + */ + private function encode(array $data): string + { + return json_encode($data, JSON_THROW_ON_ERROR); + } +} diff --git a/src/Services/ServiceException.php b/src/Services/ServiceException.php new file mode 100644 index 00000000000..d95041eae64 --- /dev/null +++ b/src/Services/ServiceException.php @@ -0,0 +1,24 @@ +errorCode; + } +} diff --git a/src/Subscription.php b/src/Subscription.php index 7a1a1c6039d..9a4b4fde8c8 100644 --- a/src/Subscription.php +++ b/src/Subscription.php @@ -10,6 +10,8 @@ final class Subscription private bool $active = true; private ?int $maxMessages = null; private int $received = 0; + private int $pendingBytes = 0; + private bool $slowConsumerSignaled = false; /** @var Connection|null Back-reference for sync operations */ private ?Connection $connection = null; @@ -19,6 +21,9 @@ public function __construct( public readonly string $subject, public readonly ?string $queue = null, private readonly ?\Closure $callback = null, + private readonly int $pendingMsgsLimit = 65536, + private readonly int $pendingBytesLimit = 67_108_864, + private readonly ?\Closure $onSlowConsumer = null, ) { $this->pendingMessages = new \SplQueue(); } @@ -35,7 +40,13 @@ public function nextMessage(?float $timeout = null): ?Message while (true) { // Drain anything already queued before blocking on the socket. if (!$this->pendingMessages->isEmpty()) { - return $this->pendingMessages->dequeue(); + $msg = $this->pendingMessages->dequeue(); + $this->pendingBytes -= \strlen((string) $msg->data); + if ($this->pendingBytes < 0) { + $this->pendingBytes = 0; + } + $this->slowConsumerSignaled = false; + return $msg; } if (!$this->active || !$this->connection instanceof \Utopia\NATS\Connection) { @@ -65,11 +76,22 @@ public function isActive(): bool public function deliver(Message $msg): void { - $this->received++; - if ($this->callback instanceof \Closure) { + $this->received++; ($this->callback)($msg); } else { + // Guard the pending queue against unbounded growth: a consumer that + // never drains its messages signals slow-consumer and the message is + // dropped rather than exhausting memory. + $msgBytes = \strlen($msg->data); + if ($this->pendingMessages->count() >= $this->pendingMsgsLimit + || ($this->pendingBytes + $msgBytes) > $this->pendingBytesLimit) { + $this->signalSlowConsumer(); + return; + } + + $this->received++; + $this->pendingBytes += $msgBytes; $this->pendingMessages->enqueue($msg); } @@ -78,6 +100,29 @@ public function deliver(Message $msg): void } } + public function getPendingCount(): int + { + return $this->pendingMessages->count(); + } + + public function getPendingBytes(): int + { + return $this->pendingBytes; + } + + private function signalSlowConsumer(): void + { + if ($this->slowConsumerSignaled) { + return; + } + + $this->slowConsumerSignaled = true; + + if ($this->onSlowConsumer instanceof \Closure) { + ($this->onSlowConsumer)($this); + } + } + public function setMaxMessages(int $max): void { $this->maxMessages = $max; diff --git a/src/Transport/SwooleTransport.php b/src/Transport/SwooleTransport.php new file mode 100644 index 00000000000..100b2e72469 --- /dev/null +++ b/src/Transport/SwooleTransport.php @@ -0,0 +1,184 @@ + $tlsOptions */ + public function __construct( + private readonly bool $secure = false, + private readonly array $tlsOptions = [], + ) {} + + public function connect(string $host, int $port, float $timeout): void + { + $flags = SWOOLE_SOCK_TCP | ($this->secure ? SWOOLE_SSL : 0); + $client = new Client($flags); + $client->set($this->buildSettings($timeout)); + + if (!$client->connect($host, $port, $timeout)) { + throw new ConnectionException("Failed to connect to {$host}:{$port}: [{$client->errCode}] {$client->errMsg}"); + } + + $this->client = $client; + } + + public function write(string $data): int + { + $client = $this->ensureConnected(); + $total = \strlen($data); + $written = 0; + + // Loop until every byte is on the wire: send() may accept a short write. + while ($written < $total) { + $chunk = $client->send($written === 0 ? $data : substr($data, $written)); + + if ($chunk === false || $chunk === 0) { + throw new ConnectionException("Failed to write to socket: [{$client->errCode}] {$client->errMsg}"); + } + + $written += $chunk; + } + + return $written; + } + + public function read(int $maxBytes, ?float $timeout = null): string + { + if ($this->buffer === '') { + $this->fill($timeout); + } + + $chunk = substr($this->buffer, 0, $maxBytes); + $this->buffer = substr($this->buffer, \strlen($chunk)); + + return $chunk; + } + + public function readLine(?float $timeout = null): string + { + $deadline = $timeout !== null ? microtime(true) + $timeout : null; + + while (($pos = strpos($this->buffer, "\n")) === false) { + $remaining = $deadline !== null ? max(0.0, $deadline - microtime(true)) : null; + $this->fill($remaining); + } + + $line = substr($this->buffer, 0, $pos + 1); + $this->buffer = substr($this->buffer, $pos + 1); + + return $line; + } + + public function upgradeTls(array $options): void + { + // Coroutine-native STARTTLS: handshake on the live connection, avoiding + // stream_socket_enable_crypto() (which misbehaves under Swoole hooks). + $client = $this->ensureConnected(); + $client->set($this->buildTlsSettings($options)); + + if (!$client->enableSSL()) { + throw new ConnectionException('Failed to upgrade connection to TLS'); + } + } + + public function isConnected(): bool + { + return $this->client instanceof \Swoole\Coroutine\Client && $this->client->isConnected(); + } + + public function close(): void + { + if ($this->client instanceof \Swoole\Coroutine\Client) { + $this->client->close(); + $this->client = null; + } + } + + private function fill(?float $timeout): void + { + $client = $this->ensureConnected(); + $data = $client->recv($timeout ?? -1); + + if ($data === '') { + throw new ConnectionException('Connection closed by server'); + } + + if ($data === false) { + // ponytail: Swoole sets errCode 110 (ETIMEDOUT) on a recv timeout; anything else is a hard error. + if ($client->errCode === 110) { + throw new TimeoutException('Read timed out'); + } + throw new ConnectionException("Failed to read from socket: [{$client->errCode}] {$client->errMsg}"); + } + + $this->buffer .= $data; + } + + private function ensureConnected(): Client + { + if (!$this->client instanceof \Swoole\Coroutine\Client) { + throw new ConnectionException('Not connected'); + } + + return $this->client; + } + + /** @return array */ + private function buildSettings(float $timeout): array + { + $settings = ['timeout' => $timeout]; + + if ($this->secure) { + $settings += $this->buildTlsSettings($this->tlsOptions); + } + + return $settings; + } + + /** + * @param array $options + * @return array + */ + private function buildTlsSettings(array $options): array + { + $settings = []; + + if (!empty($options['cafile'])) { + $settings['ssl_cafile'] = $options['cafile']; + } + if (!empty($options['local_cert'])) { + $settings['ssl_cert_file'] = $options['local_cert']; + } + if (!empty($options['local_pk'])) { + $settings['ssl_key_file'] = $options['local_pk']; + } + if (!empty($options['peer_name'])) { + $settings['ssl_host_name'] = $options['peer_name']; + } + $settings['ssl_verify_peer'] = $options['verify_peer'] ?? true; + + return $settings; + } +} diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index 8682e81ac3c..0b5657cd534 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -38,10 +38,24 @@ public function connect(string $host, int $port, float $timeout): void public function write(string $data): int { $stream = $this->ensureConnected(); - $written = @fwrite($stream, $data); + $total = \strlen($data); + $written = 0; + + // Loop until every byte is on the wire: fwrite may perform a short write. + while ($written < $total) { + $chunk = @fwrite($stream, substr($data, $written)); + + if ($chunk === false || $chunk === 0) { + if ($this->isTimedOut($stream)) { + throw new TimeoutException('Write timed out'); + } + if (feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + throw new ConnectionException('Failed to write to socket'); + } - if ($written === false) { - throw new ConnectionException('Failed to write to socket'); + $written += $chunk; } return $written; @@ -109,6 +123,9 @@ public function upgradeTls(array $options): void if (isset($options['local_pk'])) { $contextOptions['ssl']['local_pk'] = $options['local_pk']; } + if (isset($options['peer_name'])) { + $contextOptions['ssl']['peer_name'] = $options['peer_name']; + } $contextOptions['ssl']['verify_peer'] = $options['verify_peer'] ?? true; $contextOptions['ssl']['verify_peer_name'] = $options['verify_peer_name'] ?? true; diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php index 6e0603b5d59..1fbd3e87d92 100644 --- a/src/Transport/TlsTransport.php +++ b/src/Transport/TlsTransport.php @@ -48,10 +48,25 @@ public function connect(string $host, int $port, float $timeout): void public function write(string $data): int { $stream = $this->ensureConnected(); - $written = @fwrite($stream, $data); + $total = \strlen($data); + $written = 0; + + // Loop until every byte is on the wire: fwrite may perform a short write. + while ($written < $total) { + $chunk = @fwrite($stream, substr($data, $written)); + + if ($chunk === false || $chunk === 0) { + $info = stream_get_meta_data($stream); + if ($info['timed_out']) { + throw new TimeoutException('Write timed out'); + } + if (feof($stream)) { + throw new ConnectionException('Connection closed by server'); + } + throw new ConnectionException('Failed to write to TLS socket'); + } - if ($written === false) { - throw new ConnectionException('Failed to write to TLS socket'); + $written += $chunk; } return $written; @@ -144,6 +159,9 @@ private function buildSslOptions(): array if (isset($this->tlsOptions['local_pk'])) { $opts['local_pk'] = $this->tlsOptions['local_pk']; } + if (isset($this->tlsOptions['peer_name'])) { + $opts['peer_name'] = $this->tlsOptions['peer_name']; + } return $opts; } diff --git a/src/Transport/WebSocketTransport.php b/src/Transport/WebSocketTransport.php new file mode 100644 index 00000000000..479f0049f25 --- /dev/null +++ b/src/Transport/WebSocketTransport.php @@ -0,0 +1,280 @@ + $tlsOptions ssl context options when $secure + * @param string $path HTTP request path for the upgrade (NATS default "/") + */ + public function __construct( + private readonly bool $secure = false, + private readonly array $tlsOptions = [], + private readonly string $path = '/', + ) {} + + public function connect(string $host, int $port, float $timeout): void + { + $scheme = $this->secure ? 'tls' : 'tcp'; + $context = stream_context_create($this->secure ? ['ssl' => $this->tlsOptions] : []); + + $errno = 0; + $errstr = ''; + $stream = @stream_socket_client( + "{$scheme}://{$host}:{$port}", + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $context, + ); + + if ($stream === false) { + throw new ConnectionException("Failed to connect to {$host}:{$port}: [{$errno}] {$errstr}"); + } + + stream_set_blocking($stream, true); + $this->setTimeout($stream, $timeout); + $this->stream = $stream; + + $this->handshake($host, $port); + } + + public function write(string $data): int + { + $this->writeFrame($data); + + return \strlen($data); + } + + public function read(int $maxBytes, ?float $timeout = null): string + { + if ($timeout !== null) { + $this->setTimeout($this->ensureConnected(), $timeout); + } + + while ($this->buffer === '') { + $this->readFrame(); + } + + $chunk = substr($this->buffer, 0, $maxBytes); + $this->buffer = substr($this->buffer, \strlen($chunk)); + + return $chunk; + } + + public function readLine(?float $timeout = null): string + { + if ($timeout !== null) { + $this->setTimeout($this->ensureConnected(), $timeout); + } + + while (($pos = strpos($this->buffer, "\n")) === false) { + $this->readFrame(); + } + + $line = substr($this->buffer, 0, $pos + 1); + $this->buffer = substr($this->buffer, $pos + 1); + + return $line; + } + + public function upgradeTls(array $options): void + { + // wss:// negotiates TLS at connect; there is no in-band STARTTLS for WebSocket. + throw new ConnectionException('STARTTLS is not supported over WebSocket; use wss://'); + } + + public function isConnected(): bool + { + return $this->stream !== null && !feof($this->stream); + } + + public function close(): void + { + if ($this->stream !== null) { + @fclose($this->stream); + $this->stream = null; + } + } + + private function handshake(string $host, int $port): void + { + $key = base64_encode(random_bytes(16)); + $request = "GET {$this->path} HTTP/1.1\r\n" + . "Host: {$host}:{$port}\r\n" + . "Upgrade: websocket\r\n" + . "Connection: Upgrade\r\n" + . "Sec-WebSocket-Key: {$key}\r\n" + . "Sec-WebSocket-Version: 13\r\n\r\n"; + $this->rawWrite($request); + + // Read the HTTP response headers up to the blank line, keeping any trailing + // bytes (the first WS frame) — they must not be swallowed. + $response = ''; + while (!str_contains($response, "\r\n\r\n")) { + $response .= $this->rawRead(1); + } + + if (!preg_match('#^HTTP/1\.1 101#i', $response)) { + $status = strtok($response, "\r\n"); + throw new ConnectionException("WebSocket upgrade failed: {$status}"); + } + + $expected = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); + if (!preg_match('#Sec-WebSocket-Accept:\s*(.+?)\r\n#i', $response, $m) || trim($m[1]) !== $expected) { + throw new ConnectionException('WebSocket upgrade failed: bad Sec-WebSocket-Accept'); + } + } + + /** + * Read one WebSocket frame, handling control frames, and append data payloads + * (text/binary/continuation) to the buffer. + */ + private function readFrame(): void + { + $header = $this->rawRead(2); + $b0 = \ord($header[0]); + $b1 = \ord($header[1]); + + $opcode = $b0 & 0x0F; + $masked = ($b1 & 0x80) !== 0; + $len = $b1 & 0x7F; + + if ($len === 126) { + $ext = $this->rawRead(2); + $len = (\ord($ext[0]) << 8) | \ord($ext[1]); + } elseif ($len === 127) { + $ext = $this->rawRead(8); + $len = 0; + for ($i = 0; $i < 8; $i++) { + $len = ($len << 8) | \ord($ext[$i]); + } + } + + $maskKey = $masked ? $this->rawRead(4) : ''; + $payload = $len > 0 ? $this->rawRead($len) : ''; + + if ($masked && $payload !== '') { + $unmasked = ''; + for ($i = 0, $n = \strlen($payload); $i < $n; $i++) { + $unmasked .= $payload[$i] ^ $maskKey[$i % 4]; + } + $payload = $unmasked; + } + + switch ($opcode) { + case 0x0: // continuation + case 0x1: // text + case 0x2: // binary + $this->buffer .= $payload; + return; + case 0x8: // close + throw new ConnectionException('WebSocket closed by server'); + case 0x9: // ping -> pong + $this->writeFrame($payload, 0xA); + return; + case 0xA: // pong + return; + default: + throw new ConnectionException("Unexpected WebSocket opcode: {$opcode}"); + } + } + + private function writeFrame(string $payload, int $opcode = 0x2): void + { + $len = \strlen($payload); + $frame = \chr(0x80 | $opcode); // FIN + opcode + + if ($len < 126) { + $frame .= \chr(0x80 | $len); + } elseif ($len <= 0xFFFF) { + $frame .= \chr(0x80 | 126) . pack('n', $len); + } else { + $frame .= \chr(0x80 | 127) . pack('J', $len); + } + + $maskKey = random_bytes(4); + $frame .= $maskKey; + + $masked = ''; + for ($i = 0; $i < $len; $i++) { + $masked .= $payload[$i] ^ $maskKey[$i % 4]; + } + + $this->rawWrite($frame . $masked); + } + + private function rawWrite(string $data): void + { + $stream = $this->ensureConnected(); + $total = \strlen($data); + $written = 0; + + while ($written < $total) { + $chunk = @fwrite($stream, substr($data, $written)); + if ($chunk === false || $chunk === 0) { + throw new ConnectionException('Failed to write to WebSocket'); + } + $written += $chunk; + } + } + + private function rawRead(int $length): string + { + $stream = $this->ensureConnected(); + $data = ''; + + while (\strlen($data) < $length) { + $chunk = @fread($stream, $length - \strlen($data)); + if ($chunk === false || $chunk === '') { + $info = stream_get_meta_data($stream); + if ($info['timed_out']) { + throw new TimeoutException('Read timed out'); + } + throw new ConnectionException('Connection closed by server'); + } + $data .= $chunk; + } + + return $data; + } + + /** @return resource */ + private function ensureConnected() + { + if ($this->stream === null) { + throw new ConnectionException('Not connected'); + } + + return $this->stream; + } + + /** @param resource $stream */ + private function setTimeout($stream, float $timeout): void + { + $seconds = (int) $timeout; + $microseconds = (int) (($timeout - $seconds) * 1_000_000); + stream_set_timeout($stream, $seconds, $microseconds); + } +} diff --git a/tests/E2E/HardeningTest.php b/tests/E2E/HardeningTest.php new file mode 100644 index 00000000000..e02e1064f1f --- /dev/null +++ b/tests/E2E/HardeningTest.php @@ -0,0 +1,80 @@ +getServerUrl(), + drainTimeout: 20.0, + )); + + $subject = 'test.drain.' . uniqid(); + $received = 0; + $conn->subscribe($subject, function () use (&$received): void { + $received++; + }); + + $count = 25; + for ($i = 0; $i < $count; $i++) { + $conn->publish($subject, "msg-{$i}"); + } + + $start = microtime(true); + $conn->drain(); + $elapsed = microtime(true) - $start; + + $this->assertSame($count, $received, 'all pending messages drained'); + $this->assertTrue($conn->isClosed()); + $this->assertLessThan(10.0, $elapsed, 'drain completed on the PONG barrier, not the timeout'); + } + + public function testServerInfoTlsAvailableParsed(): void + { + $conn = Connection::connect($this->getServerUrl()); + $info = $conn->getServerInfo(); + + // Plaintext dev server: tls_available is parsed and reflects that TLS is + // neither available nor required on this connection. + $this->assertFalse($info->tlsAvailable); + $this->assertFalse($info->tlsRequired); + + $conn->close(); + } + + public function testTokenProviderInvokedAtConnect(): void + { + $calls = 0; + $conn = Connection::connect(new ConnectionOptions( + servers: $this->getServerUrl(), + tokenProvider: function () use (&$calls): string { + $calls++; + return 'dynamic-token'; + }, + )); + + $this->assertTrue($conn->isConnected()); + $this->assertSame(1, $calls, 'token provider resolved during the connect handshake'); + + $conn->close(); + } +} diff --git a/tests/E2E/JetStreamExtraTest.php b/tests/E2E/JetStreamExtraTest.php new file mode 100644 index 00000000000..177af0b8d29 --- /dev/null +++ b/tests/E2E/JetStreamExtraTest.php @@ -0,0 +1,210 @@ + */ + private array $streams = []; + + private function getServerUrl(): string + { + return getenv('NATS_URL') ?: 'nats://127.0.0.1:4222'; + } + + protected function setUp(): void + { + $this->conn = Connection::connect($this->getServerUrl()); + $this->js = $this->conn->jetStream(); + } + + protected function tearDown(): void + { + foreach ($this->streams as $stream) { + try { + $this->js->deleteStream($stream); + } catch (\Throwable) { + // already gone + } + } + $this->conn->close(); + } + + private function createStream(string $subject): string + { + $name = 'JSX_' . uniqid(); + $this->js->createStream(new StreamConfig( + name: $name, + subjects: [$subject], + storage: StorageType::Memory, + )); + $this->streams[] = $name; + return $name; + } + + public function testPushConsumerDeliversAndAcks(): void + { + $id = uniqid(); + $subject = "push.{$id}"; + $stream = $this->createStream($subject); + + /** @var list $received */ + $received = []; + $push = $this->js->pushSubscribe( + $stream, + new ConsumerConfig(ackPolicy: AckPolicy::Explicit), + function (JetStreamMessage $msg) use (&$received): void { + $received[] = $msg; + $msg->ack(); + }, + ); + + $this->js->publish($subject, 'hello-push'); + + $deadline = microtime(true) + 3.0; + while ($received === [] && microtime(true) < $deadline) { + $this->conn->processMessage(0.5); + } + + $this->assertCount(1, $received); + $this->assertSame('hello-push', $received[0]->getData()); + $this->assertSame($subject, $received[0]->getSubject()); + + // The ack (fire-and-forget) should clear the pending count shortly. + $ackPending = 1; + $deadline = microtime(true) + 3.0; + while ($ackPending !== 0 && microtime(true) < $deadline) { + $ackPending = $this->js->getConsumer($stream, $push->getConsumerName())->info()->numAckPending; + if ($ackPending !== 0) { + usleep(100_000); + } + } + $this->assertSame(0, $ackPending); + + $push->unsubscribe(); + } + + public function testOrderedConsumerPreservesSequence(): void + { + $id = uniqid(); + $subject = "ordered.{$id}"; + $stream = $this->createStream($subject); + + $count = 25; + for ($i = 1; $i <= $count; $i++) { + $this->js->publish($subject, "msg-{$i}"); + } + + $ordered = $this->js->orderedConsumer($stream); + + $streamSeqs = []; + $consumerSeqs = []; + for ($i = 0; $i < $count; $i++) { + $msg = $ordered->next(3.0); + $this->assertInstanceOf(JetStreamMessage::class, $msg); + $meta = $msg->metadata(); + $streamSeqs[] = $meta->streamSequence; + $consumerSeqs[] = $meta->consumerSequence; + } + + $this->assertSame(range(1, $count), $streamSeqs, 'stream sequences must be continuous and in order'); + $this->assertSame(range(1, $count), $consumerSeqs, 'consumer delivery sequences must be continuous'); + + $ordered->stop(); + } + + public function testAckSyncConfirmsAndPreventsRedelivery(): void + { + $id = uniqid(); + $subject = "acksync.{$id}"; + $stream = $this->createStream($subject); + + $this->js->publish($subject, 'ack-me'); + + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'c_' . $id, + ackPolicy: AckPolicy::Explicit, + ackWait: 1.0, + )); + + $batch = $consumer->fetch(1, 2.0); + $messages = $batch->getMessages(); + $this->assertCount(1, $messages); + + $start = microtime(true); + $messages[0]->ackSync(2.0); + $elapsed = microtime(true) - $start; + $this->assertLessThan(2.0, $elapsed, 'ackSync must return before its timeout'); + + // Wait past ack_wait, then confirm the message is not redelivered. + usleep(1_300_000); + $again = $consumer->fetch(1, 1.0); + $this->assertCount(0, $again->getMessages(), 'acked message must not be redelivered'); + } + + public function testMetadataStreamOpsAndNumPending(): void + { + $id = uniqid(); + $subject = "meta.{$id}"; + $stream = $this->createStream($subject); + + $seqs = []; + for ($i = 1; $i <= 3; $i++) { + $ack = $this->js->publish($subject, "payload-{$i}"); + $seqs[] = $ack->sequence; + } + $this->assertSame([1, 2, 3], $seqs, 'PubAck sequence numbers must be populated'); + + // getMessage by sequence returns the exact payload/subject. + $msg = $this->js->getMessage($stream, 2); + $this->assertSame('payload-2', $msg->data); + $this->assertSame($subject, $msg->subject); + $this->assertSame(2, $msg->sequence); + + // Create a consumer and consume 2 of 3 without acking. + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'm_' . $id, + ackPolicy: AckPolicy::Explicit, + ackWait: 30.0, + )); + + $before = $consumer->info(true); + $this->assertSame(3, $before->numPending, 'all messages pending before consumption'); + + $batch = $consumer->fetch(2, 2.0); + $this->assertCount(2, $batch->getMessages()); + + $after = $consumer->info(true); + $this->assertSame(2, $after->numAckPending, 'delivered-but-unacked count'); + $this->assertSame(1, $after->numPending, 'one message still awaiting delivery'); + $this->assertSame(2, $after->delivered->consumerSeq, 'expanded delivered metadata populated'); + + // no_wait fetch returns immediately with the remaining message. + $remaining = $consumer->fetch(10, 2.0, noWait: true); + $this->assertCount(1, $remaining->getMessages()); + + // deleteMessage removes it from the stream. + $this->js->deleteMessage($stream, 1); + $this->expectException(JetStreamException::class); + $this->js->getMessage($stream, 1); + } +} diff --git a/tests/E2E/JetStreamParityTest.php b/tests/E2E/JetStreamParityTest.php new file mode 100644 index 00000000000..8be7d666390 --- /dev/null +++ b/tests/E2E/JetStreamParityTest.php @@ -0,0 +1,259 @@ + */ + private array $streams = []; + + private function getServerUrl(): string + { + return getenv('NATS_URL') ?: 'nats://127.0.0.1:4222'; + } + + protected function setUp(): void + { + $this->conn = Connection::connect($this->getServerUrl()); + $this->js = $this->conn->jetStream(); + } + + protected function tearDown(): void + { + foreach ($this->streams as $stream) { + try { + $this->js->deleteStream($stream); + } catch (\Throwable) { + // already gone + } + } + $this->conn->close(); + } + + private function track(string $name): string + { + $this->streams[] = $name; + return $name; + } + + public function testStreamMetadataRepublishAndSubjectTransform(): void + { + $id = uniqid(); + $name = $this->track("PARITY_MRT_{$id}"); + + $stream = $this->js->createStream(new StreamConfig( + name: $name, + subjects: ["rp.{$id}.>"], + storage: StorageType::Memory, + metadata: ['env' => 'test', 'team' => 'core'], + republish: new Republish(source: "rp.{$id}.>", destination: "pub.{$id}.>", headersOnly: false), + subjectTransform: new SubjectTransform(source: "rp.{$id}.>", destination: "rp.{$id}.>"), + )); + + $config = $stream->info()->config; + + $this->assertIsArray($config->metadata); + $this->assertSame('test', $config->metadata['env'] ?? null); + $this->assertSame('core', $config->metadata['team'] ?? null); + + $this->assertInstanceOf(Republish::class, $config->republish); + $this->assertSame("rp.{$id}.>", $config->republish->source); + $this->assertSame("pub.{$id}.>", $config->republish->destination); + + $this->assertInstanceOf(SubjectTransform::class, $config->subjectTransform); + $this->assertSame("rp.{$id}.>", $config->subjectTransform->source); + + // Republish must forward stored messages to the destination subject. + $sub = $this->conn->subscribe("pub.{$id}.>"); + $this->js->publish("rp.{$id}.one", 'hello-republish'); + + $received = $sub->nextMessage(3.0); + $this->assertInstanceOf(\Utopia\NATS\Message::class, $received, 'republish should forward the message'); + $this->assertSame('hello-republish', $received->data); + $sub->unsubscribe(); + } + + public function testMirrorAndSourceReflectAndReplicate(): void + { + $id = uniqid(); + $origin = $this->track("PARITY_ORIG_{$id}"); + $this->js->createStream(new StreamConfig( + name: $origin, + subjects: ["ev.{$id}.>"], + storage: StorageType::Memory, + )); + + for ($i = 1; $i <= 5; $i++) { + $this->js->publish("ev.{$id}.a", "m-{$i}"); + } + + // Mirror. + $mirrorName = $this->track("PARITY_MIR_{$id}"); + $mirror = $this->js->createStream(new StreamConfig( + name: $mirrorName, + storage: StorageType::Memory, + mirror: new StreamSource(name: $origin), + )); + $this->assertInstanceOf(StreamSource::class, $mirror->info()->config->mirror); + $this->assertSame($origin, $mirror->info()->config->mirror->name); + + // Source. + $sourceName = $this->track("PARITY_SRC_{$id}"); + $sourced = $this->js->createStream(new StreamConfig( + name: $sourceName, + storage: StorageType::Memory, + sources: [new StreamSource(name: $origin)], + )); + $sources = $sourced->info()->config->sources; + $this->assertIsArray($sources); + $this->assertCount(1, $sources); + $this->assertSame($origin, $sources[0]->name); + + // Both should replicate the 5 origin messages. + $mirrorMsgs = $this->waitForMessages($mirrorName, 5); + $sourceMsgs = $this->waitForMessages($sourceName, 5); + $this->assertSame(5, $mirrorMsgs, 'mirror must replicate all origin messages'); + $this->assertSame(5, $sourceMsgs, 'source must replicate all origin messages'); + } + + private function waitForMessages(string $stream, int $expected): int + { + $count = 0; + $deadline = microtime(true) + 5.0; + while ($count < $expected && microtime(true) < $deadline) { + $count = $this->js->getStreamInfo($stream)->state->messages; + if ($count < $expected) { + usleep(100_000); + } + } + return $count; + } + + public function testConsumerMetadataIsSurfaced(): void + { + $id = uniqid(); + $name = $this->track("PARITY_CM_{$id}"); + $this->js->createStream(new StreamConfig( + name: $name, + subjects: ["cm.{$id}.>"], + storage: StorageType::Memory, + )); + + $consumer = $this->js->createConsumer($name, new ConsumerConfig( + durableName: "dur_{$id}", + metadata: ['owner' => 'billing', 'tier' => 'gold'], + )); + + $info = $consumer->info(); + $this->assertIsArray($info->metadata); + $this->assertSame('billing', $info->metadata['owner'] ?? null); + $this->assertSame('gold', $info->metadata['tier'] ?? null); + $this->assertSame('billing', $info->config->metadata['owner'] ?? null); + } + + public function testGetConsumersReturnsTypedInfos(): void + { + $id = uniqid(); + $name = $this->track("PARITY_CL_{$id}"); + $this->js->createStream(new StreamConfig( + name: $name, + subjects: ["cl.{$id}.>"], + storage: StorageType::Memory, + )); + + $this->js->createConsumer($name, new ConsumerConfig(durableName: "one_{$id}")); + $this->js->createConsumer($name, new ConsumerConfig(durableName: "two_{$id}")); + + $consumers = $this->js->getConsumers($name); + $this->assertCount(2, $consumers); + foreach ($consumers as $c) { + $this->assertInstanceOf(ConsumerInfo::class, $c); + $this->assertSame($name, $c->streamName); + } + + $names = array_map(static fn(ConsumerInfo $c): string => $c->name, $consumers); + sort($names); + $this->assertSame(["one_{$id}", "two_{$id}"], $names); + } + + public function testPerMessageTtl(): void + { + $id = uniqid(); + $name = $this->track("PARITY_TTL_{$id}"); + + $config = new StreamConfig( + name: $name, + subjects: ["ttl.{$id}.>"], + storage: StorageType::Memory, + allowMsgTtl: true, + subjectDeleteMarkerTtl: 2.0, + ); + + // Client serialization must carry the ADR-43 keys regardless of server support. + $wire = $config->toArray(); + $this->assertTrue($wire['allow_msg_ttl']); + $this->assertSame(StreamConfig::secondsToNanos(2.0), $wire['subject_delete_marker_ttl']); + + $this->js->createStream($config); + + // Publish with a per-message TTL; the Nats-TTL header must be stored. + $ack = $this->js->publish("ttl.{$id}.a", 'expires-soon', ttl: 2); + $this->assertSame(1, $ack->sequence); + + $stored = $this->js->getMessage($name, 1); + $this->assertInstanceOf(\Utopia\NATS\Headers::class, $stored->headers); + $this->assertSame('2s', $stored->headers->get('Nats-TTL')); + + // The message must actually expire (server is NATS 2.11+, per docker-compose). + $expired = false; + $deadline = microtime(true) + 6.0; + while (microtime(true) < $deadline) { + if ($this->js->getStreamInfo($name)->state->messages === 0) { + $expired = true; + break; + } + usleep(200_000); + } + $this->assertTrue($expired, 'message with TTL must expire'); + } + + public function testAccountInfoReturnsTypedObject(): void + { + $id = uniqid(); + $name = $this->track("PARITY_ACC_{$id}"); + $this->js->createStream(new StreamConfig( + name: $name, + subjects: ["acc.{$id}.>"], + storage: StorageType::Memory, + )); + + $info = $this->js->accountInfo(); + $this->assertInstanceOf(AccountInfo::class, $info); + $this->assertGreaterThanOrEqual(1, $info->streams); + $this->assertGreaterThanOrEqual(0, $info->memory); + $this->assertGreaterThanOrEqual(0, $info->apiTotal); + $this->assertArrayHasKey('memory', $info->raw); + } +} diff --git a/tests/E2E/KeyValueWatchOptionsTest.php b/tests/E2E/KeyValueWatchOptionsTest.php new file mode 100644 index 00000000000..76bf6d43c27 --- /dev/null +++ b/tests/E2E/KeyValueWatchOptionsTest.php @@ -0,0 +1,201 @@ +conn = Connection::connect($url); + $this->js = $this->conn->jetStream(); + $this->bucket = 'kvwo_' . uniqid(); + $this->kv = $this->js->createKeyValue(new KeyValueConfig( + bucket: $this->bucket, + history: 10, + )); + } + + protected function tearDown(): void + { + try { + $this->js->deleteKeyValue($this->bucket); + } catch (\Throwable) { + // ignore + } + $this->conn->close(); + } + + private function pumpUntil(callable $done, float $seconds = 3.0): void + { + $deadline = microtime(true) + $seconds; + while (!$done() && microtime(true) < $deadline) { + $this->conn->processMessage(0.2); + } + } + + public function testWatchIncludeHistoryThenLiveUpdates(): void + { + $this->kv->put('k', 'v1'); + $this->kv->put('k', 'v2'); + $this->kv->put('k', 'v3'); + $this->kv->delete('k'); + + /** @var list $received */ + $received = []; + $initDone = false; + $countAtInit = null; + + $sub = $this->kv->watch( + 'k', + function (KeyValueEntry $entry) use (&$received): void { + $received[] = $entry; + }, + new KeyValueWatchOptions(includeHistory: true), + function () use (&$initDone, &$received, &$countAtInit): void { + $initDone = true; + $countAtInit = \count($received); + }, + ); + $this->conn->flush(); + + $this->pumpUntil(function () use (&$initDone): bool { + return $initDone; + }); + + $this->assertTrue($initDone, 'onInitDone should fire after historical replay'); + $this->assertSame(4, $countAtInit, 'all four historical entries delivered before init-done'); + + $values = array_map(static fn(KeyValueEntry $e): string => $e->value, \array_slice($received, 0, 3)); + $this->assertSame(['v1', 'v2', 'v3'], $values); + $this->assertSame(KeyValueOperation::Delete, $received[3]->operation); + + // Live update after the historical set. + $this->kv->put('k', 'v4'); + $this->pumpUntil(function () use (&$received): bool { + return \count($received) >= 5; + }); + $sub->unsubscribe(); + + $this->assertGreaterThanOrEqual(5, \count($received)); + $this->assertSame('v4', $received[4]->value); + $this->assertSame(KeyValueOperation::Put, $received[4]->operation); + } + + public function testWatchIgnoreDeletesSkipsMarkers(): void + { + $this->kv->put('k', 'v1'); + $this->kv->delete('k'); + + $ops = []; + $initDone = false; + + $sub = $this->kv->watch( + 'k', + function ($entry) use (&$ops): void { + $ops[] = $entry->operation; + }, + new KeyValueWatchOptions(includeHistory: true, ignoreDeletes: true), + function () use (&$initDone): void { + $initDone = true; + }, + ); + $this->conn->flush(); + + $this->pumpUntil(function () use (&$initDone): bool { + return $initDone; + }); + $sub->unsubscribe(); + + $this->assertTrue($initDone); + $this->assertContains(KeyValueOperation::Put, $ops); + $this->assertNotContains(KeyValueOperation::Delete, $ops); + $this->assertNotContains(KeyValueOperation::Purge, $ops); + } + + public function testWatchMetaOnlyDeliversNoValue(): void + { + $this->kv->put('k', 'a-real-value'); + + $entries = []; + $initDone = false; + + $sub = $this->kv->watch( + 'k', + function ($entry) use (&$entries): void { + $entries[] = $entry; + }, + new KeyValueWatchOptions(includeHistory: true, metaOnly: true), + function () use (&$initDone): void { + $initDone = true; + }, + ); + $this->conn->flush(); + + $this->pumpUntil(function () use (&$initDone): bool { + return $initDone; + }); + $sub->unsubscribe(); + + $this->assertCount(1, $entries); + $this->assertSame('k', $entries[0]->key); + $this->assertSame('', $entries[0]->value, 'metaOnly entries carry no value body'); + $this->assertGreaterThan(0, $entries[0]->revision); + } + + public function testPurgeDeletesRemovesTombstones(): void + { + $this->kv->put('a', '1'); + $this->kv->put('b', '2'); + $this->kv->put('c', '3'); + $this->kv->delete('a'); + $this->kv->purge('b'); + // 'c' stays live. + + // Markers keep 'a' and 'b' present before purging. + $keysBefore = $this->kv->keys(); + sort($keysBefore); + $this->assertSame(['a', 'b', 'c'], $keysBefore); + $valuesBefore = $this->kv->status()->values; + + $removed = $this->kv->purgeDeletes(); + + $this->assertSame(2, $removed); + + $keysAfter = $this->kv->keys(); + sort($keysAfter); + $this->assertSame(['c'], $keysAfter); + + $valuesAfter = $this->kv->status()->values; + $this->assertLessThan($valuesBefore, $valuesAfter); + $this->assertSame(1, $valuesAfter); + } + + public function testPurgeDeletesRespectsThreshold(): void + { + $this->kv->put('a', '1'); + $this->kv->delete('a'); + + // Marker was just created; "older than 60s" keeps it. + $removed = $this->kv->purgeDeletes(60.0); + + $this->assertSame(0, $removed); + $this->assertContains('a', $this->kv->keys()); + } +} diff --git a/tests/E2E/KeyValueWatchTest.php b/tests/E2E/KeyValueWatchTest.php new file mode 100644 index 00000000000..46b326c3fc9 --- /dev/null +++ b/tests/E2E/KeyValueWatchTest.php @@ -0,0 +1,137 @@ +conn = Connection::connect($url); + $this->js = $this->conn->jetStream(); + $this->bucket = 'kvw_' . uniqid(); + $this->kv = $this->js->createKeyValue(new KeyValueConfig( + bucket: $this->bucket, + history: 10, + )); + } + + protected function tearDown(): void + { + try { + $this->js->deleteKeyValue($this->bucket); + } catch (\Throwable) { + // ignore + } + $this->conn->close(); + } + + public function testHistoryReturnsAllRevisionsInOrder(): void + { + $r1 = $this->kv->put('color', 'red'); + $r2 = $this->kv->put('color', 'green'); + $r3 = $this->kv->put('color', 'blue'); + $this->kv->delete('color'); + + $history = $this->kv->history('color'); + + $this->assertCount(4, $history); + $this->assertSame('red', $history[0]->value); + $this->assertSame('green', $history[1]->value); + $this->assertSame('blue', $history[2]->value); + + $this->assertSame($r1, $history[0]->revision); + $this->assertSame($r2, $history[1]->revision); + $this->assertSame($r3, $history[2]->revision); + + // Revisions strictly increasing. + $this->assertTrue($r1 < $r2 && $r2 < $r3); + + $this->assertSame(KeyValueOperation::Put, $history[0]->operation); + $this->assertSame(KeyValueOperation::Delete, $history[3]->operation); + } + + public function testGetRevisionFetchesSpecificSeq(): void + { + $r1 = $this->kv->put('name', 'alice'); + $this->kv->put('name', 'bob'); + + $entry = $this->kv->getRevision('name', $r1); + + $this->assertSame('alice', $entry->value); + $this->assertSame($r1, $entry->revision); + $this->assertSame('name', $entry->key); + } + + public function testGetRevisionRejectsSeqFromAnotherKey(): void + { + // 'alpha' revision seq must not resolve when requested under 'beta'. + $alphaSeq = $this->kv->put('alpha', 'a-value'); + $this->kv->put('beta', 'b-value'); + + $this->expectException(KeyValueException::class); + $this->kv->getRevision('beta', $alphaSeq); + } + + public function testWatchFiresCallbackOnPut(): void + { + $received = []; + $sub = $this->kv->watch('greeting', function ($entry) use (&$received): void { + $received[] = $entry; + }); + + // Ensure the consumer/subscription is established before producing. + $this->conn->flush(); + + $this->kv->put('greeting', 'hello'); + + $deadline = microtime(true) + 3.0; + while ($received === [] && microtime(true) < $deadline) { + $this->conn->processMessage(0.2); + } + + $sub->unsubscribe(); + + $this->assertCount(1, $received); + $this->assertSame('greeting', $received[0]->key); + $this->assertSame('hello', $received[0]->value); + $this->assertSame(KeyValueOperation::Put, $received[0]->operation); + } + + public function testWatchFiresCallbackOnDelete(): void + { + $this->kv->put('flag', 'on'); + + $ops = []; + $sub = $this->kv->watch('flag', function ($entry) use (&$ops): void { + $ops[] = $entry->operation; + }); + $this->conn->flush(); + + $this->kv->delete('flag'); + + $deadline = microtime(true) + 3.0; + while ($ops === [] && microtime(true) < $deadline) { + $this->conn->processMessage(0.2); + } + + $sub->unsubscribe(); + + $this->assertContains(KeyValueOperation::Delete, $ops); + } +} diff --git a/tests/E2E/ObjectStoreExtraTest.php b/tests/E2E/ObjectStoreExtraTest.php new file mode 100644 index 00000000000..542d3e6d2c4 --- /dev/null +++ b/tests/E2E/ObjectStoreExtraTest.php @@ -0,0 +1,180 @@ +conn = Connection::connect($url); + $this->js = $this->conn->jetStream(); + $this->bucket = 'objx_' . uniqid(); + $this->store = ObjectStore::createOrUpdate($this->conn, $this->js, new ObjectStoreConfig( + bucket: $this->bucket, + )); + } + + protected function tearDown(): void + { + try { + $this->js->deleteStream("OBJ_{$this->bucket}"); + } catch (\Throwable) { + // ignore + } + $this->conn->close(); + } + + public function testWatchFiresOnPutAndDelete(): void + { + /** @var list $events */ + $events = []; + $sub = $this->store->watch(function (ObjectMeta $meta) use (&$events): void { + $events[] = $meta; + }); + + // Ensure the consumer/subscription is established before producing. + $this->conn->flush(); + + $this->store->put('watched.txt', 'hello'); + $this->pumpUntil(fn(): bool => $events !== []); + + $this->assertCount(1, $events); + $this->assertSame('watched.txt', $events[0]->name); + $this->assertFalse($events[0]->deleted); + + $this->store->delete('watched.txt'); + $this->pumpUntil(fn(): bool => \count($events) >= 2); + + $sub->unsubscribe(); + + $this->assertCount(2, $events); + $this->assertSame('watched.txt', $events[1]->name); + $this->assertTrue($events[1]->deleted); + } + + public function testWatchIncludeHistoryDeliversCurrentMetaFirst(): void + { + $this->store->put('existing.txt', 'already here'); + + /** @var list $events */ + $events = []; + $sub = $this->store->watch(function (ObjectMeta $meta) use (&$events): void { + $events[] = $meta; + }, includeHistory: true); + + $this->conn->flush(); + $this->pumpUntil(fn(): bool => $events !== []); + + $sub->unsubscribe(); + + $this->assertNotEmpty($events); + $this->assertSame('existing.txt', $events[0]->name); + } + + public function testAddLinkResolvesToTargetBytes(): void + { + $this->store->put('target.bin', 'the real payload'); + + $linkMeta = $this->store->addLink('alias.bin', 'target.bin'); + $this->assertInstanceOf(\Utopia\NATS\ObjectStore\ObjectLink::class, $linkMeta->link); + $this->assertSame('target.bin', $linkMeta->link->name); + + // get() on the link transparently returns the target's bytes. + $this->assertSame('the real payload', $this->store->get('alias.bin')); + } + + public function testBucketLinkCannotBeRead(): void + { + $this->store->addBucketLink('otherbucket', 'SOME_OTHER_BUCKET'); + + $this->expectException(ObjectStoreException::class); + $this->store->get('otherbucket'); + } + + public function testUpdateMetaChangesDescriptionAndKeepsBytes(): void + { + $payload = 'immutable bytes'; + $this->store->put('doc.txt', $payload); + + $updated = $this->store->updateMeta('doc.txt', description: 'a helpful description'); + $this->assertSame('a helpful description', $updated->description); + + // Meta round-trips the new description... + $this->assertSame('a helpful description', $this->store->getMeta('doc.txt')->description); + + // ...and the bytes are untouched. + $this->assertSame($payload, $this->store->get('doc.txt')); + } + + public function testSealThenPutThrows(): void + { + $this->store->put('before.txt', 'written before seal'); + + $this->store->seal(); + + $threw = false; + try { + $this->store->put('after.txt', 'should be rejected'); + } catch (\Throwable) { + $threw = true; + } + + $this->assertTrue($threw, 'put() after seal() should fail because the stream is sealed'); + + // Data written before sealing is still readable. + $this->assertSame('written before seal', $this->store->get('before.txt')); + } + + /** + * @param callable(): bool $done + */ + private function pumpUntil(callable $done, float $timeout = 3.0): void + { + $deadline = microtime(true) + $timeout; + while (!$done() && microtime(true) < $deadline) { + $this->conn->processMessage(0.2); + } + } + + public function testDeleteConflictLeavesReplacementIntact(): void + { + // A stale delete must not corrupt a concurrent replacement: it should conflict + // on the guarded tombstone publish BEFORE purging any chunks. + $this->store->put('doc', 'v1'); + + $readMeta = new \ReflectionMethod($this->store, 'readMetaWithSeq'); + [$staleMeta, $staleSeq] = $readMeta->invoke($this->store, 'doc'); + + // A writer replaces the object, advancing the meta subject past $staleSeq. + $this->store->put('doc', 'v2'); + + // Replay the stale delete: it expects $staleSeq but the subject moved on. + $deleteVersion = new \ReflectionMethod($this->store, 'deleteVersion'); + $conflicted = false; + try { + $deleteVersion->invoke($this->store, $staleMeta, $staleSeq); + } catch (ObjectStoreException) { + $conflicted = true; + } + + $this->assertTrue($conflicted, 'stale delete should have conflicted'); + // The replacement is intact — its chunks were never purged by the stale delete. + $this->assertSame('v2', $this->store->get('doc')); + } +} diff --git a/tests/E2E/ObjectStoreTest.php b/tests/E2E/ObjectStoreTest.php new file mode 100644 index 00000000000..ee4e7efa582 --- /dev/null +++ b/tests/E2E/ObjectStoreTest.php @@ -0,0 +1,151 @@ +conn = Connection::connect($url); + $this->js = $this->conn->jetStream(); + $this->bucket = 'obj_' . uniqid(); + $this->store = ObjectStore::createOrUpdate($this->conn, $this->js, new ObjectStoreConfig( + bucket: $this->bucket, + )); + } + + protected function tearDown(): void + { + try { + $this->js->deleteStream("OBJ_{$this->bucket}"); + } catch (\Throwable) { + // ignore + } + $this->conn->close(); + } + + public function testRoundTripMultiChunkPayload(): void + { + // ~300KB => spans multiple 128KB chunks. + $payload = random_bytes(300 * 1024); + + $putMeta = $this->store->put('big.bin', $payload); + $this->assertGreaterThan(1, $putMeta->chunks); + $this->assertSame(\strlen($payload), $putMeta->size); + + $fetched = $this->store->get('big.bin'); + $this->assertSame($payload, $fetched); + + $meta = $this->store->getMeta('big.bin'); + $this->assertSame($putMeta->digest, $meta->digest); + + // Digest matches an independent computation. + $expectedDigest = 'SHA-256=' . rtrim(strtr(base64_encode(hash('sha256', $payload, true)), '+/', '-_'), '='); + $this->assertSame($expectedDigest, $meta->digest); + } + + public function testSmallObjectRoundTrip(): void + { + $this->store->put('hello.txt', 'hello world'); + $this->assertSame('hello world', $this->store->get('hello.txt')); + } + + public function testListReturnsStoredObjects(): void + { + $this->store->put('a.txt', 'aaa'); + $this->store->put('b.txt', 'bbb'); + + $names = array_map(fn(\Utopia\NATS\ObjectStore\ObjectMeta $m): string => $m->name, $this->store->list()); + sort($names); + + $this->assertSame(['a.txt', 'b.txt'], $names); + } + + public function testDeleteRemovesObject(): void + { + $this->store->put('temp.txt', 'gone soon'); + $this->assertSame('gone soon', $this->store->get('temp.txt')); + + $this->store->delete('temp.txt'); + + $this->expectException(\RuntimeException::class); + $this->store->get('temp.txt'); + } + + public function testOverwriteReplacesData(): void + { + $this->store->put('file', 'version-one'); + $this->store->put('file', 'version-two-longer'); + + $this->assertSame('version-two-longer', $this->store->get('file')); + $this->assertCount(1, $this->store->list()); + } + + public function testStatusReportsBucketStream(): void + { + $this->store->put('x.txt', 'data'); + $info = $this->store->status(); + $this->assertSame("OBJ_{$this->bucket}", $info->config->name); + $this->assertGreaterThan(0, $info->state->messages); + } + + public function testSingleWriterOverwriteReclaimsOldChunks(): void + { + // Each version is a single chunk. After overwrite, only the new chunk + // plus one rolled-up meta record should remain (2 messages total); + // a leftover previous chunk would push the count to 3. + $this->store->put('reclaim.bin', 'version-one'); + $this->store->put('reclaim.bin', 'version-two'); + + $this->assertSame('version-two', $this->store->get('reclaim.bin')); + $this->assertSame(2, $this->store->status()->state->messages); + } + + public function testConcurrentOverwriteConflictsInsteadOfOrphaning(): void + { + // Deterministically drive the optimistic-concurrency conflict rather than + // racing two real writers (which serialises on a fast host). Capture the meta + // sequence a stale writer would hold, let a second write advance the subject, + // then replay the stale write via the seq-aware seam and assert it conflicts + // and cleans up its own chunks. + $this->store->put('conflict.bin', 'v1'); + + $readMeta = new \ReflectionMethod($this->store, 'readMetaWithSeq'); + [$stalePrev, $staleSeq] = $readMeta->invoke($this->store, 'conflict.bin'); + + // A second writer wins, advancing the meta subject past $staleSeq. + $this->store->put('conflict.bin', 'v2'); + + // Replay the stale write: it expects $staleSeq but the subject moved on. + $writeVersion = new \ReflectionMethod($this->store, 'writeVersion'); + $conflicted = false; + try { + $writeVersion->invoke($this->store, 'conflict.bin', 'v3-stale', $stalePrev, $staleSeq); + } catch (ObjectStoreException) { + $conflicted = true; + } + + $this->assertTrue($conflicted, 'stale write should have conflicted'); + + // Winner intact, and no orphaned chunks: only v2's single chunk plus the + // rolled-up meta remain (the stale attempt purged its own chunk). + $this->assertSame('v2', $this->store->get('conflict.bin')); + $this->assertCount(1, $this->store->list()); + $this->assertSame(2, $this->store->status()->state->messages); + } +} diff --git a/tests/E2E/OrderedConsumerTest.php b/tests/E2E/OrderedConsumerTest.php new file mode 100644 index 00000000000..e45a099e417 --- /dev/null +++ b/tests/E2E/OrderedConsumerTest.php @@ -0,0 +1,142 @@ + teardown() + create() and recreate the + * ephemeral consumer from the message after the last good one. Iteration must + * recover and still yield every message exactly once, in stream order, with no + * loss and no duplicate handed to the caller past the reset. + * + * If the reset logic is broken (e.g. it recreated from the wrong stream + * sequence, or did not recreate at all) this test fails: the collected stream + * sequences would show a gap or a duplicate. + * + * Requires a JetStream-enabled server (NATS_URL). + */ +final class OrderedConsumerTest extends TestCase +{ + private Connection $conn; + private JetStream $js; + /** @var list */ + private array $streams = []; + + private function getServerUrl(): string + { + return getenv('NATS_URL') ?: 'nats://127.0.0.1:4222'; + } + + protected function setUp(): void + { + $this->conn = Connection::connect($this->getServerUrl()); + $this->js = $this->conn->jetStream(); + } + + protected function tearDown(): void + { + foreach ($this->streams as $stream) { + try { + $this->js->deleteStream($stream); + } catch (\Throwable) { + // already gone + } + } + $this->conn->close(); + } + + private function createStream(string $subject): string + { + $name = 'OC_' . uniqid(); + $this->js->createStream(new StreamConfig( + name: $name, + subjects: [$subject], + storage: StorageType::Memory, + )); + $this->streams[] = $name; + return $name; + } + + private function setExpectedConsumerSeq(OrderedConsumer $ordered, int $value): void + { + $ref = new \ReflectionProperty(OrderedConsumer::class, 'expectedConsumerSeq'); + $ref->setValue($ordered, $value); + } + + private function getExpectedConsumerSeq(OrderedConsumer $ordered): int + { + $ref = new \ReflectionProperty(OrderedConsumer::class, 'expectedConsumerSeq'); + return (int) $ref->getValue($ordered); + } + + public function testResetRecreatesAndRecoversInOrder(): void + { + $id = uniqid(); + $subject = "orderedreset.{$id}"; + $stream = $this->createStream($subject); + + $count = 20; + for ($i = 1; $i <= $count; $i++) { + $this->js->publish($subject, "msg-{$i}"); + } + + $ordered = $this->js->orderedConsumer($stream); + + // Consume the first half normally. These stream sequences are 1..half. + $half = 10; + $streamSeqs = []; + for ($i = 0; $i < $half; $i++) { + $msg = $ordered->next(3.0); + $this->assertInstanceOf(JetStreamMessage::class, $msg); + $streamSeqs[] = $msg->metadata()->streamSequence; + } + $this->assertSame(range(1, $half), $streamSeqs, 'first half must arrive in order before the reset'); + + $consumerBefore = $ordered->getConsumerName(); + + // Simulate a missed delivery: advance the expected consumer sequence by + // one so the very next pushed message (whose consumer sequence is what + // we would otherwise expect) reads as a gap and forces a reset. + $expected = $this->getExpectedConsumerSeq($ordered); + $this->setExpectedConsumerSeq($ordered, $expected + 1); + + // Drain the rest. The first next() here observes the gap, triggers + // reset($lastStreamSeq + 1) and recreates the consumer from the message + // after the last good one (stream seq $half + 1), then returns it. + for ($i = 0; $i < $count - $half; $i++) { + $msg = $ordered->next(3.0); + $this->assertInstanceOf(JetStreamMessage::class, $msg); + $streamSeqs[] = $msg->metadata()->streamSequence; + } + + $consumerAfter = $ordered->getConsumerName(); + + // The reset must have torn down and recreated the ephemeral consumer. + $this->assertNotSame($consumerBefore, $consumerAfter, 'reset must recreate the ephemeral consumer'); + + // Every message delivered to the caller exactly once, in stream order, + // with no loss and no duplicate across the reset boundary. + $this->assertSame(range(1, $count), $streamSeqs, 'all messages must be delivered once, in order, across the reset'); + + // No further messages remain. + $this->assertNotInstanceOf(\Utopia\NATS\JetStream\JetStreamMessage::class, $ordered->next(0.5), 'no extra or duplicate messages after recovery'); + + $ordered->stop(); + } +} diff --git a/tests/E2E/RequestManyTest.php b/tests/E2E/RequestManyTest.php new file mode 100644 index 00000000000..79236fa4a57 --- /dev/null +++ b/tests/E2E/RequestManyTest.php @@ -0,0 +1,113 @@ +subscribe($subject, function ($msg) use ($conn, $tag): void { + if ($msg->replyTo !== null) { + $conn->publish($msg->replyTo, $tag); + } + }); + } + } + + public function testCollectsAllResponders(): void + { + $conn = Connection::connect($this->getServerUrl()); + $subject = 'test.rm.all.' . uniqid(); + $this->attachResponders($conn, $subject); + + $replies = $conn->requestMany($subject, 'ping', ['timeout' => 2.0]); + + $this->assertCount(3, $replies); + $bodies = array_map(fn(\Utopia\NATS\Message $m): string => $m->data, $replies); + sort($bodies); + $this->assertSame(['r1', 'r2', 'r3'], $bodies); + + $conn->close(); + } + + public function testMaxStopsEarly(): void + { + $conn = Connection::connect($this->getServerUrl()); + $subject = 'test.rm.max.' . uniqid(); + $this->attachResponders($conn, $subject); + + $replies = $conn->requestMany($subject, 'ping', ['max' => 2, 'timeout' => 2.0]); + + $this->assertCount(2, $replies); + + $conn->close(); + } + + public function testTimeoutBoundsWhenMaxUnreached(): void + { + $conn = Connection::connect($this->getServerUrl()); + $subject = 'test.rm.timeout.' . uniqid(); + $this->attachResponders($conn, $subject); + + // Ask for more replies than exist: the overall timeout is the stop + // condition, and we still collect the three that did answer. + $start = microtime(true); + $replies = $conn->requestMany($subject, 'ping', ['max' => 10, 'timeout' => 0.8]); + $elapsed = microtime(true) - $start; + + $this->assertCount(3, $replies); + $this->assertGreaterThanOrEqual(0.7, $elapsed); + + $conn->close(); + } + + public function testStallStopsBeforeTimeout(): void + { + $conn = Connection::connect($this->getServerUrl()); + $subject = 'test.rm.stall.' . uniqid(); + $this->attachResponders($conn, $subject); + + // Three fast replies then silence. A short stall window returns well + // before the generous overall timeout would. + $start = microtime(true); + $replies = $conn->requestMany($subject, 'ping', ['timeout' => 3.0, 'stall' => 0.3]); + $elapsed = microtime(true) - $start; + + $this->assertCount(3, $replies); + $this->assertLessThan(1.5, $elapsed); + + $conn->close(); + } + + public function testEmptyWhenNoResponders(): void + { + $conn = Connection::connect($this->getServerUrl()); + $subject = 'test.rm.none.' . uniqid(); + + // No subscribers: the server's 503 no-responders reply means zero + // responders, so requestMany yields an empty list. + $replies = $conn->requestMany($subject, 'ping', ['timeout' => 2.0]); + + $this->assertSame([], $replies); + + $conn->close(); + } +} diff --git a/tests/E2E/ServiceExtrasTest.php b/tests/E2E/ServiceExtrasTest.php new file mode 100644 index 00000000000..66480262dda --- /dev/null +++ b/tests/E2E/ServiceExtrasTest.php @@ -0,0 +1,156 @@ +conn = Connection::connect($url); + $this->name = 'svcx_' . uniqid(); + + $this->service = new Service( + $this->conn, + $this->name, + '1.0.0', + 'Extras service', + ['owner' => 'levi', 'env' => 'test'], + ); + } + + protected function tearDown(): void + { + $this->service->stop(); + $this->conn->close(); + } + + public function testGroupedEndpointIsReachableAtPrefixedSubject(): void + { + // Root group carries the unique service name so subjects don't collide. + $group = $this->service->addGroup($this->name)->addGroup('math'); + $group->addEndpoint('add', fn(Message $msg): string => 'sum:' . $msg->data); + + // Nested group prefixes cumulatively. + $sub = $group->addGroup('trig'); + $sub->addEndpoint('sin', fn(Message $msg): string => 'sin:' . $msg->data); + + $this->service->start(); + + $r1 = $this->conn->request("{$this->name}.math.add", '2', 2.0); + $this->assertSame('sum:2', $r1->data); + + $r2 = $this->conn->request("{$this->name}.math.trig.sin", '0', 2.0); + $this->assertSame('sin:0', $r2->data); + } + + public function testCustomErrorCodeSurfacesInHeadersAndCountsError(): void + { + $this->service->addEndpoint('fail', "{$this->name}.fail", function (Message $msg): string { + throw new ServiceException('418', 'I am a teapot'); + }); + $this->service->start(); + + $response = $this->conn->request("{$this->name}.fail", 'x', 2.0); + + $this->assertInstanceOf(\Utopia\NATS\Headers::class, $response->headers); + $this->assertSame('418', $response->headers->get('Nats-Service-Error-Code')); + $this->assertSame('I am a teapot', $response->headers->get('Nats-Service-Error')); + + // Error count is tracked in stats. + $stats = json_decode($this->conn->request("\$SRV.STATS.{$this->name}", '', 2.0)->data, true); + $byName = []; + foreach ($stats['endpoints'] as $ep) { + $byName[$ep['name']] = $ep; + } + $this->assertSame(1, $byName['fail']['num_requests']); + $this->assertSame(1, $byName['fail']['num_errors']); + } + + public function testInfoIncludesEndpointsWithSubjectsAndMetadata(): void + { + $group = $this->service->addGroup($this->name)->addGroup('v1'); + $group->addEndpoint( + 'status', + fn(Message $msg): string => 'ok', + null, + null, + ['visibility' => 'public'], + ); + $this->service->start(); + + $info = json_decode($this->conn->request("\$SRV.INFO.{$this->name}", '', 2.0)->data, true); + + // Service-level metadata. + $this->assertSame('levi', $info['metadata']['owner']); + $this->assertSame('test', $info['metadata']['env']); + + // Endpoint appears with its prefixed subject and its own metadata. + $byName = []; + foreach ($info['endpoints'] as $ep) { + $byName[$ep['name']] = $ep; + } + $this->assertSame("{$this->name}.v1.status", $byName['status']['subject']); + $this->assertSame('public', $byName['status']['metadata']['visibility']); + } + + public function testCustomQueueGroupIsHonored(): void + { + $queue = 'workers_' . uniqid(); + + // Group-level queue group is inherited by its endpoints. + $group = $this->service->addGroup('jobs', $queue); + $group->addEndpoint('run', fn(Message $msg): string => 'done'); + + // Per-endpoint override on the bare service. + $this->service->addEndpoint( + 'direct', + "{$this->name}.direct", + fn(Message $msg): string => 'direct', + $queue, + ); + $this->service->start(); + + $info = json_decode($this->conn->request("\$SRV.INFO.{$this->name}", '', 2.0)->data, true); + $byName = []; + foreach ($info['endpoints'] as $ep) { + $byName[$ep['name']] = $ep; + } + $this->assertSame($queue, $byName['run']['queue_group']); + $this->assertSame($queue, $byName['direct']['queue_group']); + } + + public function testQueueGroupLoadBalancesAcrossInstances(): void + { + $queue = 'lb_' . uniqid(); + $subject = "{$this->name}.work"; + + // Two endpoints on the same subject + queue group behave as two queue + // members: NATS delivers each request to only one of them. + $this->service->addEndpoint('a', $subject, fn(Message $msg): string => 'a', $queue); + $this->service->addEndpoint('b', $subject, fn(Message $msg): string => 'b', $queue); + $this->service->start(); + + $seen = ['a' => 0, 'b' => 0]; + for ($i = 0; $i < 30; $i++) { + $reply = $this->conn->request($subject, (string) $i, 2.0)->data; + $seen[$reply]++; + } + + $this->assertSame(30, $seen['a'] + $seen['b']); + $this->assertGreaterThan(0, $seen['a']); + $this->assertGreaterThan(0, $seen['b']); + } +} diff --git a/tests/E2E/ServiceTest.php b/tests/E2E/ServiceTest.php new file mode 100644 index 00000000000..127d4177aea --- /dev/null +++ b/tests/E2E/ServiceTest.php @@ -0,0 +1,94 @@ +conn = Connection::connect($url); + $this->name = 'svc_' . uniqid(); + $this->echoSubject = "{$this->name}.echo"; + + $this->service = new Service($this->conn, $this->name, '1.2.3', 'Test service'); + $this->service->addEndpoint('echo', $this->echoSubject, fn(Message $msg): string => 'echo:' . $msg->data); + $this->service->addEndpoint('boom', "{$this->name}.boom", function (Message $msg): string { + throw new \RuntimeException('kaboom'); + }); + $this->service->start(); + } + + protected function tearDown(): void + { + $this->service->stop(); + $this->conn->close(); + } + + public function testEndpointHandlesRequest(): void + { + $response = $this->conn->request($this->echoSubject, 'hello', 2.0); + $this->assertSame('echo:hello', $response->data); + } + + public function testPingReturnsServiceIdentity(): void + { + $response = $this->conn->request('$SRV.PING', '', 2.0); + $data = json_decode($response->data, true); + + $this->assertSame('io.nats.micro.v1.ping_response', $data['type']); + $this->assertSame($this->name, $data['name']); + $this->assertSame('1.2.3', $data['version']); + $this->assertArrayHasKey('id', $data); + $this->assertSame($this->service->getId(), $data['id']); + } + + public function testInfoListsEndpoints(): void + { + $response = $this->conn->request("\$SRV.INFO.{$this->name}", '', 2.0); + $data = json_decode($response->data, true); + + $this->assertSame('io.nats.micro.v1.info_response', $data['type']); + $this->assertSame('Test service', $data['description']); + + $subjects = array_map(fn(array $e) => $e['subject'], $data['endpoints']); + $this->assertContains($this->echoSubject, $subjects); + } + + public function testStatsTrackRequestAndErrorCounts(): void + { + // Drive one successful request and one failing request. + $this->conn->request($this->echoSubject, 'a', 2.0); + $this->conn->request($this->echoSubject, 'b', 2.0); + $this->conn->request("{$this->name}.boom", 'x', 2.0); + + $response = $this->conn->request("\$SRV.STATS.{$this->name}", '', 2.0); + $data = json_decode($response->data, true); + + $this->assertSame('io.nats.micro.v1.stats_response', $data['type']); + $this->assertArrayHasKey('started', $data); + + $byName = []; + foreach ($data['endpoints'] as $ep) { + $byName[$ep['name']] = $ep; + } + + $this->assertSame(2, $byName['echo']['num_requests']); + $this->assertSame(0, $byName['echo']['num_errors']); + $this->assertSame(1, $byName['boom']['num_requests']); + $this->assertSame(1, $byName['boom']['num_errors']); + $this->assertGreaterThanOrEqual(0, $byName['echo']['processing_time']); + } +} diff --git a/tests/E2E/TlsTest.php b/tests/E2E/TlsTest.php new file mode 100644 index 00000000000..2b0846720ef --- /dev/null +++ b/tests/E2E/TlsTest.php @@ -0,0 +1,70 @@ +url = getenv('NATS_TLS_URL') ?: 'tls://127.0.0.1:14223'; + $this->certs = __DIR__ . '/../fixtures/certs'; + + // Skip when the TLS server isn't running (e.g. local runs without the + // nats-tls compose service); CI brings it up so the tests execute there. + $host = parse_url($this->url, PHP_URL_HOST) ?: '127.0.0.1'; + $port = parse_url($this->url, PHP_URL_PORT) ?: 14223; + $probe = @fsockopen($host, (int) $port, $errno, $errstr, 1.0); + if ($probe === false) { + $this->markTestSkipped("TLS server not reachable at {$this->url}"); + } + fclose($probe); + } + + public function testMutualTlsConnectAndRoundTrip(): void + { + $conn = Connection::connect(new ConnectionOptions( + servers: $this->url, + tlsCaFile: "{$this->certs}/ca.pem", + tlsCertFile: "{$this->certs}/client-cert.pem", + tlsKeyFile: "{$this->certs}/client-key.pem", + )); + + $this->assertTrue($conn->isConnected()); + + $sub = $conn->subscribe('tls.echo'); + $conn->publish('tls.echo', 'secure-hello'); + $msg = $sub->nextMessage(2.0); + + $this->assertInstanceOf(\Utopia\NATS\Message::class, $msg); + $this->assertSame('secure-hello', $msg->data); + + $conn->close(); + } + + public function testConnectWithoutClientCertIsRejected(): void + { + // Server requires a client certificate (verify: true); connecting with only + // the CA must fail the TLS handshake. + $this->expectException(ConnectionException::class); + + Connection::connect(new ConnectionOptions( + servers: $this->url, + tlsCaFile: "{$this->certs}/ca.pem", + )); + } +} diff --git a/tests/E2E/WebSocketTest.php b/tests/E2E/WebSocketTest.php new file mode 100644 index 00000000000..cb4999a6711 --- /dev/null +++ b/tests/E2E/WebSocketTest.php @@ -0,0 +1,68 @@ +url = getenv('NATS_WS_URL') ?: 'ws://127.0.0.1:14224'; + + $host = parse_url($this->url, PHP_URL_HOST) ?: '127.0.0.1'; + $port = parse_url($this->url, PHP_URL_PORT) ?: 14224; + $probe = @fsockopen($host, (int) $port, $errno, $errstr, 1.0); + if ($probe === false) { + $this->markTestSkipped("WebSocket server not reachable at {$this->url}"); + } + fclose($probe); + } + + public function testConnectAndRoundTripOverWebSocket(): void + { + $conn = Connection::connect(new ConnectionOptions( + servers: $this->url, + transportFactory: fn(string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), + )); + + $this->assertTrue($conn->isConnected()); + + $sub = $conn->subscribe('ws.echo'); + $conn->publish('ws.echo', 'hello-over-ws'); + $msg = $sub->nextMessage(2.0); + + $this->assertInstanceOf(\Utopia\NATS\Message::class, $msg); + $this->assertSame('hello-over-ws', $msg->data); + + $conn->close(); + } + + public function testRequestReplyOverWebSocket(): void + { + $conn = Connection::connect(new ConnectionOptions( + servers: $this->url, + transportFactory: fn(string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), + )); + + $conn->subscribe('ws.service', function ($msg) use ($conn): void { + $conn->publish($msg->replyTo, 'pong:' . $msg->data); + }); + + $reply = $conn->request('ws.service', 'ping', 2.0); + $this->assertSame('pong:ping', $reply->data); + + $conn->close(); + } +} diff --git a/tests/Unit/Auth/NKeyAuthTest.php b/tests/Unit/Auth/NKeyAuthTest.php new file mode 100644 index 00000000000..dc4e7cfa1bf --- /dev/null +++ b/tests/Unit/Auth/NKeyAuthTest.php @@ -0,0 +1,133 @@ +markTestSkipped('sodium extension required'); + } + } + + public function testDerivesPublicKeyMatchingSodium(): void + { + $rawSeed = random_bytes(32); + $keyPair = sodium_crypto_sign_seed_keypair($rawSeed); + $publicRaw = sodium_crypto_sign_publickey($keyPair); + + $seedString = $this->encodeUserSeed($rawSeed); + $auth = new NKeyAuth('', $seedString); + + $nkey = $auth->publicKey(); + + // A user public NKey is 'U' + base32(1 prefix byte + 32-byte key + 2 CRC). + $this->assertSame('U', $nkey[0]); + $this->assertSame(56, \strlen($nkey)); + + $decoded = $this->base32Decode($nkey); + $this->assertSame(35, \strlen($decoded)); + $this->assertSame(160, \ord($decoded[0]), 'user role prefix byte'); + // The 32-byte payload must be exactly sodium's public key. + $this->assertSame($publicRaw, substr($decoded, 1, 32)); + } + + public function testAuthenticateSignatureVerifiesAgainstDerivedKey(): void + { + $rawSeed = random_bytes(32); + $keyPair = sodium_crypto_sign_seed_keypair($rawSeed); + $publicRaw = sodium_crypto_sign_publickey($keyPair); + + $auth = new NKeyAuth('', $this->encodeUserSeed($rawSeed)); + $nonce = 'server-nonce-' . bin2hex(random_bytes(8)); + + $result = $auth->authenticate($nonce); + + $this->assertSame($auth->publicKey(), $result['nkey']); + $this->assertNotSame('', $result['nkey']); + + $signature = $this->base32Decode($result['sig']); + $this->assertTrue( + sodium_crypto_sign_verify_detached($signature, $nonce, $publicRaw), + 'signature must verify against the derived public key', + ); + } + + public function testExplicitPublicKeyIsPreserved(): void + { + $rawSeed = random_bytes(32); + $auth = new NKeyAuth('UEXPLICITKEY', $this->encodeUserSeed($rawSeed)); + + $this->assertSame('UEXPLICITKEY', $auth->publicKey()); + } + + /** + * Encode a raw 32-byte seed as a user NKey seed string. The seed's own CRC is + * never validated by the client, so a zero CRC is fine here. + */ + private function encodeUserSeed(string $rawSeed): string + { + $userRole = 160; // PrefixByteUser + $seedMarker = 144; // PrefixByteSeed + $b1 = $seedMarker | ($userRole >> 5); + $b2 = ($userRole & 31) << 3; + + $raw = \chr($b1) . \chr($b2) . $rawSeed . "\x00\x00"; + + return $this->base32Encode($raw); + } + + private function base32Encode(string $input): string + { + $output = ''; + $buffer = 0; + $bitsLeft = 0; + + for ($i = 0, $len = \strlen($input); $i < $len; $i++) { + $buffer = ($buffer << 8) | \ord($input[$i]); + $bitsLeft += 8; + while ($bitsLeft >= 5) { + $bitsLeft -= 5; + $output .= self::ALPHABET[($buffer >> $bitsLeft) & 0x1F]; + } + } + + if ($bitsLeft > 0) { + $output .= self::ALPHABET[($buffer << (5 - $bitsLeft)) & 0x1F]; + } + + return $output; + } + + private function base32Decode(string $input): string + { + $output = ''; + $buffer = 0; + $bitsLeft = 0; + + for ($i = 0, $len = \strlen($input); $i < $len; $i++) { + $val = strpos(self::ALPHABET, $input[$i]); + $buffer = ($buffer << 5) | $val; + $bitsLeft += 5; + if ($bitsLeft >= 8) { + $bitsLeft -= 8; + $output .= \chr(($buffer >> $bitsLeft) & 0xFF); + } + } + + return $output; + } +} diff --git a/tests/Unit/ConnectionExtrasTest.php b/tests/Unit/ConnectionExtrasTest.php new file mode 100644 index 00000000000..e866fedc62f --- /dev/null +++ b/tests/Unit/ConnectionExtrasTest.php @@ -0,0 +1,113 @@ +assertInstanceOf( + AuthenticationException::class, + Connection::mapServerError('Authorization Violation'), + ); + } + + public function testUserAuthenticationExpiredMapsToAuthenticationException(): void + { + $this->assertInstanceOf( + AuthenticationException::class, + Connection::mapServerError('User Authentication Expired'), + ); + } + + public function testMaximumPayloadMapsToMaxPayloadException(): void + { + $this->assertInstanceOf( + MaxPayloadException::class, + Connection::mapServerError('Maximum Payload Exceeded'), + ); + } + + public function testPermissionsViolationForSubscriptionMapsToPermissionException(): void + { + $this->assertInstanceOf( + PermissionException::class, + Connection::mapServerError("Permissions Violation for Subscription to 'foo.bar'"), + ); + } + + public function testPermissionsViolationForPublishMapsToPermissionException(): void + { + $this->assertInstanceOf( + PermissionException::class, + Connection::mapServerError("Permissions Violation for Publish to 'foo.bar'"), + ); + } + + public function testUnknownErrorMapsToProtocolException(): void + { + $this->assertInstanceOf( + ProtocolException::class, + Connection::mapServerError('some unexpected error'), + ); + } + + public function testLameDuckInfoInvokesCallback(): void + { + $fired = false; + $fake = new FakeTransport(); + $conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + onLameDuck: function () use (&$fired): void { + $fired = true; + }, + transportFactory: fn(string $scheme): FakeTransport => $fake, + )); + + // Server signals lame-duck mode via an asynchronous INFO. With only one + // known server there is nowhere to fail over, so the callback fires and + // the connection is left intact. + $fake->pushInbound('INFO {"server_id":"FAKE","ldm":true}' . "\r\n"); + $conn->processMessage(1.0); + + $this->assertTrue($fired); + $this->assertFalse($conn->isReconnecting()); + + $conn->close(); + } + + public function testNonLameDuckInfoDoesNotInvokeCallback(): void + { + $fired = false; + $fake = new FakeTransport(); + $conn = Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + onLameDuck: function () use (&$fired): void { + $fired = true; + }, + transportFactory: fn(string $scheme): FakeTransport => $fake, + )); + + $fake->pushInbound('INFO {"server_id":"FAKE","ldm":false}' . "\r\n"); + $conn->processMessage(1.0); + + $this->assertFalse($fired); + + $conn->close(); + } +} diff --git a/tests/Unit/ConnectionProtocolTest.php b/tests/Unit/ConnectionProtocolTest.php new file mode 100644 index 00000000000..8ad3727b1b1 --- /dev/null +++ b/tests/Unit/ConnectionProtocolTest.php @@ -0,0 +1,114 @@ + 'nats://127.0.0.1:4222', + 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + ], $extra); + + return Connection::connect(new ConnectionOptions(...$args)); + } + + public function testConnectNegotiatesHeadersWhenSupported(): void + { + $fake = new FakeTransport(['headers' => true]); + $conn = $this->connect($fake); + + $payload = $fake->connectPayload(); + $this->assertTrue($payload['headers']); + $this->assertTrue($payload['no_responders']); + + $conn->close(); + } + + public function testConnectDisablesHeadersWhenNotSupported(): void + { + $fake = new FakeTransport(['headers' => false]); + $conn = $this->connect($fake); + + $payload = $fake->connectPayload(); + $this->assertFalse($payload['headers']); + $this->assertFalse($payload['no_responders']); + + $conn->close(); + } + + public function testPublishWithHeadersUsesHpub(): void + { + $fake = new FakeTransport(['headers' => true]); + $conn = $this->connect($fake); + + $headers = new Headers(); + $headers->set('X-Key', 'value'); + $conn->publish('subj', 'hello', null, $headers); + + $this->assertStringContainsString('HPUB subj', $fake->written); + + $conn->close(); + } + + public function testPublishWithHeadersRejectedWhenServerLacksSupport(): void + { + $fake = new FakeTransport(['headers' => false]); + $conn = $this->connect($fake); + + $headers = new Headers(); + $headers->set('X-Key', 'value'); + + $this->expectException(ProtocolException::class); + try { + $conn->publish('subj', 'hello', null, $headers); + } finally { + $conn->close(); + } + } + + public function testMaxPayloadIncludesHeaderBytes(): void + { + $fake = new FakeTransport(['headers' => true, 'max_payload' => 40]); + $conn = $this->connect($fake); + + $headers = new Headers(); + $headers->set('X-Long-Header', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + + // Body alone (5 bytes) is well under 40, but header block + body exceeds it. + $this->assertGreaterThan(40, \strlen($headers->toWire()) + 5); + + $this->expectException(MaxPayloadException::class); + try { + $conn->publish('subj', 'hello', null, $headers); + } finally { + $conn->close(); + } + } + + public function testTlsAvailableIsParsed(): void + { + $fake = new FakeTransport(['tls_available' => true, 'tls_required' => false]); + $conn = $this->connect($fake); + + $this->assertTrue($conn->getServerInfo()->tlsAvailable); + $this->assertFalse($conn->getServerInfo()->tlsRequired); + + $conn->close(); + } +} diff --git a/tests/Unit/ConnectionTlsAndAuthTest.php b/tests/Unit/ConnectionTlsAndAuthTest.php new file mode 100644 index 00000000000..d13a2340a5a --- /dev/null +++ b/tests/Unit/ConnectionTlsAndAuthTest.php @@ -0,0 +1,100 @@ + 'nats://127.0.0.1:4222', + 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + ], $extra); + + return Connection::connect(new ConnectionOptions(...$args)); + } + + public function testTlsOptionDefaults(): void + { + $options = new ConnectionOptions(); + $this->assertTrue($options->tlsVerify); + $this->assertNull($options->tlsServerName); + } + + public function testConnectionTlsOptionsMapping(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'tlsVerify' => false, + 'tlsServerName' => 'example.com', + ]); + + $method = new \ReflectionMethod(Connection::class, 'tlsOptions'); + /** @var array $tls */ + $tls = $method->invoke($conn); + + $this->assertFalse($tls['verify_peer']); + $this->assertFalse($tls['verify_peer_name']); + $this->assertSame('example.com', $tls['peer_name']); + + $conn->close(); + } + + public function testTlsTransportAppliesVerifyAndSni(): void + { + $transport = new TlsTransport([ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'peer_name' => 'example.com', + ]); + + $method = new \ReflectionMethod(TlsTransport::class, 'buildSslOptions'); + /** @var array $ssl */ + $ssl = $method->invoke($transport); + + $this->assertFalse($ssl['verify_peer']); + $this->assertFalse($ssl['verify_peer_name']); + $this->assertSame('example.com', $ssl['peer_name']); + } + + public function testTokenProviderResolvedAtConnect(): void + { + $calls = 0; + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'tokenProvider' => function () use (&$calls): string { + $calls++; + return 'token-' . $calls; + }, + ]); + + $this->assertSame(1, $calls, 'token provider invoked once at connect'); + $this->assertSame('token-1', $fake->connectPayload()['auth_token']); + + $conn->close(); + } + + public function testJwtProviderResolvedAtConnect(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'jwtProvider' => fn(): string => 'my.jwt.token', + ]); + + $this->assertSame('my.jwt.token', $fake->connectPayload()['jwt']); + + $conn->close(); + } +} diff --git a/tests/Unit/JetStream/ConfigTest.php b/tests/Unit/JetStream/ConfigTest.php new file mode 100644 index 00000000000..d0bec7d46df --- /dev/null +++ b/tests/Unit/JetStream/ConfigTest.php @@ -0,0 +1,75 @@ +toArray(); + $this->assertSame('deliver.here', $arr['deliver_subject']); + $this->assertSame('group-a', $arr['deliver_group']); + $this->assertTrue($arr['flow_control']); + $this->assertSame(2_000_000_000, $arr['idle_heartbeat']); + } + + public function testConsumerConfigRoundTrip(): void + { + $config = ConsumerConfig::fromArray([ + 'deliver_subject' => 'x.y', + 'flow_control' => true, + 'idle_heartbeat' => 5_000_000_000, + ]); + + $this->assertSame('x.y', $config->deliverSubject); + $this->assertTrue($config->flowControl); + $this->assertEqualsWithDelta(5.0, $config->idleHeartbeat, PHP_FLOAT_EPSILON); + } + + public function testConsumerInfoPopulatesSequences(): void + { + $info = ConsumerInfo::fromArray([ + 'stream_name' => 'S', + 'name' => 'C', + 'num_pending' => 7, + 'num_ack_pending' => 3, + 'delivered' => ['consumer_seq' => 4, 'stream_seq' => 10], + 'ack_floor' => ['consumer_seq' => 1, 'stream_seq' => 7], + ]); + + $this->assertSame(7, $info->numPending); + $this->assertSame(3, $info->numAckPending); + $this->assertInstanceOf(SequenceInfo::class, $info->delivered); + $this->assertSame(4, $info->delivered->consumerSeq); + $this->assertSame(10, $info->delivered->streamSeq); + $this->assertSame(7, $info->ackFloor->streamSeq); + } + + public function testStreamMessageDecodesBase64Payload(): void + { + $msg = StreamMessage::fromArray([ + 'subject' => 'foo.bar', + 'seq' => 42, + 'data' => base64_encode('the-payload'), + ]); + + $this->assertSame('foo.bar', $msg->subject); + $this->assertSame(42, $msg->sequence); + $this->assertSame('the-payload', $msg->data); + } +} diff --git a/tests/Unit/ReconnectBackoffTest.php b/tests/Unit/ReconnectBackoffTest.php new file mode 100644 index 00000000000..29ff61e8a47 --- /dev/null +++ b/tests/Unit/ReconnectBackoffTest.php @@ -0,0 +1,62 @@ +assertEqualsWithDelta(0.0, Connection::reconnectBackoff(0, 2.0, 30.0), PHP_FLOAT_EPSILON); + } + + public function testBackoffGrowsExponentially(): void + { + // base 2.0, factor 2.0: 2, 4, 8, 16 ... + $this->assertEqualsWithDelta(2.0, Connection::reconnectBackoff(1, 2.0, 100.0), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(4.0, Connection::reconnectBackoff(2, 2.0, 100.0), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(8.0, Connection::reconnectBackoff(3, 2.0, 100.0), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(16.0, Connection::reconnectBackoff(4, 2.0, 100.0), PHP_FLOAT_EPSILON); + } + + public function testBackoffIsCapped(): void + { + $cap = 8.0; + for ($attempt = 1; $attempt <= 20; $attempt++) { + $this->assertLessThanOrEqual($cap, Connection::reconnectBackoff($attempt, 2.0, $cap)); + } + + // Well past the cap it stays pinned. + $this->assertSame($cap, Connection::reconnectBackoff(10, 2.0, $cap)); + } + + public function testCustomFactor(): void + { + $this->assertEqualsWithDelta(1.0, Connection::reconnectBackoff(1, 1.0, 100.0, 3.0), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(3.0, Connection::reconnectBackoff(2, 1.0, 100.0, 3.0), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(9.0, Connection::reconnectBackoff(3, 1.0, 100.0, 3.0), PHP_FLOAT_EPSILON); + } + + public function testBufferAcceptsUntilCap(): void + { + $cap = 100; + + $this->assertTrue(Connection::reconnectBufferAccepts(0, 50, $cap)); + $this->assertTrue(Connection::reconnectBufferAccepts(50, 50, $cap), 'exactly at cap fits'); + $this->assertFalse(Connection::reconnectBufferAccepts(50, 51, $cap), 'one byte over cap'); + $this->assertFalse(Connection::reconnectBufferAccepts(100, 1, $cap)); + } + + public function testZeroCapDisablesBuffering(): void + { + $this->assertFalse(Connection::reconnectBufferAccepts(0, 1, 0)); + $this->assertFalse(Connection::reconnectBufferAccepts(0, 1, -5)); + } +} diff --git a/tests/Unit/SubscriptionSlowConsumerTest.php b/tests/Unit/SubscriptionSlowConsumerTest.php new file mode 100644 index 00000000000..04b9ee755c2 --- /dev/null +++ b/tests/Unit/SubscriptionSlowConsumerTest.php @@ -0,0 +1,111 @@ +sid; + }, + ); + + for ($i = 0; $i < 10; $i++) { + $sub->deliver(new Message('foo', 'x')); + } + + $this->assertNotEmpty($signaled, 'slow consumer callback fired'); + $this->assertSame('1', $signaled[0]); + // Queue is bounded at the limit rather than holding all 10 messages. + $this->assertSame(3, $sub->getPendingCount()); + } + + public function testExceedingByteLimitSignals(): void + { + $fired = 0; + $sub = new Subscription( + sid: '2', + subject: 'foo', + pendingMsgsLimit: 1_000_000, + pendingBytesLimit: 10, + onSlowConsumer: function () use (&$fired): void { + $fired++; + }, + ); + + $sub->deliver(new Message('foo', str_repeat('a', 6))); + $this->assertSame(0, $fired); + $this->assertSame(6, $sub->getPendingBytes()); + + // Next 6 bytes would push past the 10-byte cap. + $sub->deliver(new Message('foo', str_repeat('a', 6))); + $this->assertSame(1, $fired); + $this->assertSame(6, $sub->getPendingBytes(), 'over-limit message dropped'); + $this->assertSame(1, $sub->getPendingCount()); + } + + public function testDrainingResetsSignalAndBytes(): void + { + $fired = 0; + $sub = new Subscription( + sid: '3', + subject: 'foo', + pendingMsgsLimit: 2, + pendingBytesLimit: 1_000_000, + onSlowConsumer: function () use (&$fired): void { + $fired++; + }, + ); + + $sub->deliver(new Message('foo', 'aa')); + $sub->deliver(new Message('foo', 'bb')); + $sub->deliver(new Message('foo', 'cc')); // dropped + signal + $this->assertSame(1, $fired); + + // Drain one, freeing a slot; the signal latch resets so a later overflow re-fires. + $sub->nextMessage(0.0); + $this->assertSame(1, $sub->getPendingCount()); + + $sub->deliver(new Message('foo', 'dd')); // back to limit + $sub->deliver(new Message('foo', 'ee')); // dropped + signal again + $this->assertSame(2, $fired); + } + + public function testCallbackSubscriptionsNeverQueue(): void + { + $received = 0; + $sub = new Subscription( + sid: '4', + subject: 'foo', + callback: function () use (&$received): void { + $received++; + }, + pendingMsgsLimit: 1, + pendingBytesLimit: 1, + ); + + for ($i = 0; $i < 5; $i++) { + $sub->deliver(new Message('foo', 'payload')); + } + + $this->assertSame(5, $received); + $this->assertSame(0, $sub->getPendingCount()); + } +} diff --git a/tests/Unit/Support/FakeTransport.php b/tests/Unit/Support/FakeTransport.php new file mode 100644 index 00000000000..28d75efd151 --- /dev/null +++ b/tests/Unit/Support/FakeTransport.php @@ -0,0 +1,131 @@ + */ + public array $writes = []; + /** @var list> */ + public array $tlsUpgrades = []; + + private string $inbound = ''; + private bool $connected = false; + + /** @param array $info Fields merged into the served INFO. */ + public function __construct(private readonly array $info = []) {} + + public function connect(string $host, int $port, float $timeout): void + { + $this->connected = true; + $this->inbound .= $this->infoLine(); + } + + public function write(string $data): int + { + $this->written .= $data; + $this->writes[] = $data; + + // Answer PINGs so the handshake / flush / drain barrier completes. + $pings = substr_count($data, "PING\r\n"); + for ($i = 0; $i < $pings; $i++) { + $this->inbound .= "PONG\r\n"; + } + + return \strlen($data); + } + + public function read(int $maxBytes, ?float $timeout = null): string + { + if ($this->inbound === '') { + throw new TimeoutException('No inbound data'); + } + + $chunk = substr($this->inbound, 0, $maxBytes); + $this->inbound = substr($this->inbound, \strlen($chunk)); + + return $chunk; + } + + public function readLine(?float $timeout = null): string + { + $pos = strpos($this->inbound, "\n"); + if ($pos === false) { + throw new TimeoutException('No inbound line'); + } + + $line = substr($this->inbound, 0, $pos + 1); + $this->inbound = substr($this->inbound, $pos + 1); + + return $line; + } + + public function upgradeTls(array $options): void + { + $this->tlsUpgrades[] = $options; + } + + public function isConnected(): bool + { + return $this->connected; + } + + public function close(): void + { + $this->connected = false; + } + + // --- Test helpers --- + + public function pushInbound(string $data): void + { + $this->inbound .= $data; + } + + /** + * Decode the CONNECT payload the client sent during the handshake. + * + * @return array + */ + public function connectPayload(): array + { + if (!preg_match('/CONNECT (\{.*?\})\r\n/', $this->written, $m)) { + throw new ConnectionException('No CONNECT sent'); + } + + return json_decode($m[1], true, 512, JSON_THROW_ON_ERROR); + } + + private function infoLine(): string + { + $info = array_merge([ + 'server_id' => 'FAKE', + 'server_name' => 'fake', + 'version' => '2.10.0', + 'proto' => 1, + 'host' => '127.0.0.1', + 'port' => 4222, + 'headers' => true, + 'auth_required' => false, + 'tls_required' => false, + 'tls_available' => false, + 'max_payload' => 1048576, + 'jetstream' => true, + ], $this->info); + + return 'INFO ' . json_encode($info, JSON_THROW_ON_ERROR) . "\r\n"; + } +} diff --git a/tests/Unit/Support/PartialWriteStream.php b/tests/Unit/Support/PartialWriteStream.php new file mode 100644 index 00000000000..255d3904d92 --- /dev/null +++ b/tests/Unit/Support/PartialWriteStream.php @@ -0,0 +1,44 @@ +assertNotFalse($stream); + + $transport = new TcpTransport(); + $prop = new \ReflectionProperty(TcpTransport::class, 'stream'); + $prop->setValue($transport, $stream); + + $data = 'HELLO NATS WORLD'; // 16 bytes, chunk size 3 + $written = $transport->write($data); + + $this->assertSame(\strlen($data), $written); + $this->assertSame($data, PartialWriteStream::$buffer, 'all bytes reached the sink'); + // 16 bytes at 3 bytes/call => ceil(16/3) = 6 fwrite calls. + $this->assertSame(6, PartialWriteStream::$writeCalls); + + fclose($stream); + } +} diff --git a/tests/fixtures/certs/ca-key.pem b/tests/fixtures/certs/ca-key.pem new file mode 100644 index 00000000000..a9362fb9e73 --- /dev/null +++ b/tests/fixtures/certs/ca-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC2iVEZqAA73ET3 +csEhFSwnyMidhZzoU4tGUAY8cPAVpDDVuryH5ZHjHrQkARXQ2zsMILgijPHcii8x +rgXEf7Hd+pGTM08fzy2Ffr82UpyP5HoMqZ3Xdrpb2+j/7VJBhF85YbFDFDFd/LJ6 +jljLyUT390zfh1wt6VE0BM4g+9/TMBKPYbh49dGInUYwoqqDznbkHJc0VoGRaBQK +BDkHPVujb+frRmiEk9jNuoZ6t20Ch8LTJnUmWLEcCOGbQNQLhVPofDd+Hzs4xY2M +PKiaVEIlEUeSqgtkkZCwUfPBuB2RiJeI5GZkgbTwpWKS4lI96Knkg4amZkrXoZaP +h6yK1uNhAgMBAAECggEABcO3uZ1c8dxWmpCXhB7yDQjsj6TyO+1ffvH0sHX0mbUH +YciK6akYqVkv4S8zltaa5RrSFdfo+h5XDTTss7RAqWefWfcLLvr7pLVIU+6EnpAI +QVwd7aRZ5aAVRKJ5sDNTpTFDnYBcFWwZ4Crx/PA3QF7L9lyE+beI5qFRjShx0hkr +Ceup5G2zMA1UZqpSr+tg8mKr6AFem6pegiwr5V57hSxjPDmjsF0sc1nzoSnXH/3s +PrtPFSobiEXywJp7KWPHL8uAHErAvyUgqpd/NpZE+oMSxcGhl2P5Q2lq6etweDX3 +dxg3U8Bt+TvUcmX02AtOo5or2M+0KEcYjnSXSNKmkQKBgQD/hN3boQhov8ULdOEg +Crwbxm4DCJwSv6ds1XBZmwd0vTt7pwZ9dzgIPddr9qfZJYh0R6t9JZBRz3eAcqPp +m8ra46OBKncxUTXPeQeWvEabP5rjThJ8m4cJM6ACaT5gvGolLwjyFwEQjDxye91Y +/CaLZbyc1ngp7A2KBWK0s2vJJQKBgQC24Ue7BeiXcmcektkp4ZUyXkE9oHV1/TZY +OScOmyAQQTY6TUtunkJwjcadnVF4LVl9YlilkOahtyvzC9iwEe/A77vnYB/5LXZz +RsQ0cwqyI1fOAQGu5rLK2epY1WbDwuHgwjWyZlp0m0VQIPKDgKhD/CQfAG57oSrO +34O06hqSjQKBgDnZ5Tkyrmq48G31mnrcs1Y+iO3a5ys/hTKY6IUBGqENrwgB+vAx +K9iB1+IzyDDb1JcvGt6MwK4lINNOIVpk0XrZi01VRgM+dcZxioxQOFQyBGluZbUc +qWgl7hZdqafRQN8GVSoqly0d8xmgfa8F99wbvqZr0Gz9attS+NQcw0yZAoGAMM+o +KY7GKcXrKAnh1H/VJoBpwV0WfMt8ucdnWUqmIDCHnMAMphWvfgMZQ/A9UmooPIMH +TgmCxR7yqAg9aaEf8MsLyBGr9wHfX50/uRALhshQIze5j0kb53CKT/OFXjKhvy+W +zWrsMbtPoLdqgxbP0nV13qBq3KyDlHur15m0RfECgYAF0xX0AbcHMslV7ajPhgk1 +DjowCEv+e+68oOVKIfaBJxUMf/hbbJhILZcyBovHhWKJEt4zwTZ7ilb/c3HrslD6 ++UnkCXg3gvqZcn9s8w/8kSbAdXAloXhiVvR8qTyGaTsJic60dcV4zH/wELp6S0mF +zW5ZxeTugAgtjo2D4FKlTw== +-----END PRIVATE KEY----- diff --git a/tests/fixtures/certs/ca.pem b/tests/fixtures/certs/ca.pem new file mode 100644 index 00000000000..29a63f97739 --- /dev/null +++ b/tests/fixtures/certs/ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUcSUzcxuJw5B9bBcUM9oU65+A/tgwDQYJKoZIhvcNAQEL +BQAwFzEVMBMGA1UEAwwMbmF0cy10ZXN0LWNhMCAXDTI2MDgxMTExMTQzOFoYDzIx +MjYwNzE4MTExNDM4WjAXMRUwEwYDVQQDDAxuYXRzLXRlc3QtY2EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2iVEZqAA73ET3csEhFSwnyMidhZzoU4tG +UAY8cPAVpDDVuryH5ZHjHrQkARXQ2zsMILgijPHcii8xrgXEf7Hd+pGTM08fzy2F +fr82UpyP5HoMqZ3Xdrpb2+j/7VJBhF85YbFDFDFd/LJ6jljLyUT390zfh1wt6VE0 +BM4g+9/TMBKPYbh49dGInUYwoqqDznbkHJc0VoGRaBQKBDkHPVujb+frRmiEk9jN +uoZ6t20Ch8LTJnUmWLEcCOGbQNQLhVPofDd+Hzs4xY2MPKiaVEIlEUeSqgtkkZCw +UfPBuB2RiJeI5GZkgbTwpWKS4lI96Knkg4amZkrXoZaPh6yK1uNhAgMBAAGjUzBR +MB0GA1UdDgQWBBTpbolZGopxa06csvvhcRaymzmDNzAfBgNVHSMEGDAWgBTpbolZ +Gopxa06csvvhcRaymzmDNzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQARVLyEyJllnDp4rs4y3Y93z9znBI+fX/DJE23X0DWXZYHdG2hD/RXnDIAb +vFDJX2Jq9lmXsKTWq1l4jA3hlYcJVJSBqRhm03DdG4y2o9sHeudMYUsU3CigfzGb +0w/pk5/6O+GbqtLTrU3m0umMS40osxM4FpAp1hl+GG98Lrx56N6MuTaOrq6Tf6ar +SwcChWxsMY8/0r/w10CtK3scRESEfJffmE6Q6fgjLmhtwcbdnMGh80btlrhsvrrR +MOIHpBMbVsush5wEDwMqGY5u4gmhswc9rm9S8nwf4O21PU43QXvJjKkmUrvnWyJg +iU9afTPKr3wn9kGjyLZ/6QxGAGWa +-----END CERTIFICATE----- diff --git a/tests/fixtures/certs/client-cert.pem b/tests/fixtures/certs/client-cert.pem new file mode 100644 index 00000000000..d80350555a7 --- /dev/null +++ b/tests/fixtures/certs/client-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGTCCAgGgAwIBAgIUeaIyHLsYIv+B4URfEjrWzO4PeiwwDQYJKoZIhvcNAQEL +BQAwFzEVMBMGA1UEAwwMbmF0cy10ZXN0LWNhMCAXDTI2MDgxMTExMTQzOFoYDzIx +MjYwNzE4MTExNDM4WjAbMRkwFwYDVQQDDBBuYXRzLXRlc3QtY2xpZW50MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAj/FIOX9XjANFJad0V4+DsCWdRt +Y9jjg9S3UfQT5dVjmrCnjp3ON6MsrTDi1jNT1PxXNkiF6VsORSG54NAgVOg0N835 +dcHn14iPxudsbkKptd55AUKsHNYJPdZkkFEWPgeLn/FKxYtaW1m0AQxha5HoNZtR +bIFrk6Rl6K09GDme+Fa+f3Dr4kf11hK40dYJN+JVkpyGEZ1ynm/cvP7ZFKkxPxwT +yEpF3QHpf33RXEpvMTJ6GIolcZntlo1bS7WhLau/jkFo6gcUWDKl1RnOV4Lasg3+ +5zdEe8sPCQGPtRGnms3gf4k/LcZnChh6tc8X8XcdrZiMKNdLCfJWJjOu3wIDAQAB +o1cwVTATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQU45iN6bX5AQpApk16 +qvd67P9sMEQwHwYDVR0jBBgwFoAU6W6JWRqKcWtOnLL74XEWsps5gzcwDQYJKoZI +hvcNAQELBQADggEBADZWxjitWyI5Vt8cEUWupA6BdBFDlLbJuxiIVwUmyH4RqWTl +0jh5dxlQdGdDPCzkhKHzLOcNLmRNkISPqzcszqlquAm+aFygdWROLpwBUb73kgdZ +WrocrpWRsBhfNqr2mQEk+p33qHi0D52894gUbI+woLlwNa28z3tx9nttGs3gHF6o +86gQRIjQW8c4PcnFrol9z2FX31aOHgDHHuWcbGLFxEycDOn7nSLAex/uITr/1Ens +yU5G6mRlaezXdPIPUghxUgCCLh2pUl3znKURx9AhT+RVV/Oo4hY19buN0C4AJ/km +t2YEpH+Vk84wmLXdSKQwpko1qlbvQ/agPhTS2sU= +-----END CERTIFICATE----- diff --git a/tests/fixtures/certs/client-key.pem b/tests/fixtures/certs/client-key.pem new file mode 100644 index 00000000000..716a0630970 --- /dev/null +++ b/tests/fixtures/certs/client-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDECP8Ug5f1eMA0 +Ulp3RXj4OwJZ1G1j2OOD1LdR9BPl1WOasKeOnc43oyytMOLWM1PU/Fc2SIXpWw5F +Ibng0CBU6DQ3zfl1wefXiI/G52xuQqm13nkBQqwc1gk91mSQURY+B4uf8UrFi1pb +WbQBDGFrkeg1m1FsgWuTpGXorT0YOZ74Vr5/cOviR/XWErjR1gk34lWSnIYRnXKe +b9y8/tkUqTE/HBPISkXdAel/fdFcSm8xMnoYiiVxme2WjVtLtaEtq7+OQWjqBxRY +MqXVGc5XgtqyDf7nN0R7yw8JAY+1EaeazeB/iT8txmcKGHq1zxfxdx2tmIwo10sJ +8lYmM67fAgMBAAECggEAOZGixzV19MKHP1ZH9urSAaPiptq5XS8ecWKQi2KwTTiD +TbQ/IKS7IJ+syReoJdtg7RVFz00w9jylTiv/WEQGmqiuXQ0CC1DscuFJx9HCiAbP +q/cvde1MehNpF13UnoURkRnKKShFGwo3eMg5Em2+Vka8+LEqEkFXhD4tJ01ouan+ +qLJ9XI45S72tVvRAqx7zaIlmXzVUevDiRgyXnz589BguRbKex9Dgnz1oqNSULQbS +t4Jp6m5ohjh2ChN30eYR48DB3+7UlS0ZX3jVC5JLaThxE4VZBSeUrZjQ51L0Q3kp +IIADiN5A4L4P9DawqU11MrG52G/5+6eAF5rUluA8MQKBgQDkq5K5Dz6ju0q5xcbd +DcC9tVbuXVdHVOP8N4eKjVo8HYwEIbWPgTvnORrThN+sqZgMqXMagE9zKVY7mJFd +u+fte/PYTtSWdxbhUZD5JB6ywcz2HRZL3bPx6QYDzRwxlcfqltVoN+BTG/Ki5yzx +ndXFhNSWZIcycB8pMkm9SnIMowKBgQDbduqD0JxnJSrRUwWa6pUrD6mPDRqhnd+f +HmBbmxBupE+BDHgYSlwEj6hN5SjI0Rc2oYmQNqwJ7jodYYn/kLxwf2CEg91zt0JD +FiNpAhNNqHDhheeY7tlOMdypHtjzF8suIrwpGQ3pBg5jxuRXUdVtjqyhYXcMQjyX +cHQbYFWclQKBgA6XfDde56pfCzerjTMR65ivd6qiPi2JFztC9905g+aOJB8nX4SW +xJHOrQ2OTw+fitpT/cpVIbzBiIUpCS2oDfazkC3faeVoGAeswsmDKopRn5ic5o1U +ymTHb+DSN9haLHWlxlxuJFxpTiRjCBUq8zc1cLNgGth3GyXv1ZDErV5tAoGAX37G +GudaNxOYJw9uH/O1RtCA4ms8TFjq5k0Mdq/pnYtHwQqQ81jf42gzPvnZHlDFPhNa +TXNMtuF/TiuUJa1wIa97aw4J/YEfhxuJ+vZZOfBydd+TYBuGWR0fPEPc+SJAWenp +YCSzxdLJzibMUyabrRlpCqvjZmUoAml/TC5F8mUCgYEA2tZaJVS8v7v/mXDC/BsD +pFtGhMDNQugDw0eiFKd/LMh++7cCEMP1+MyJfbs5pm8ImuOx4Zpj1YUw3SPQil1j +BPcN6mez/uFh+IrPLl5AUeuUft/Cgdl96Qgfw7j4bvVVjYeDdQlDwIsYTIffWpBl +1X95UoWygyZCP50IcQbBDB8= +-----END PRIVATE KEY----- diff --git a/tests/fixtures/certs/server-cert.pem b/tests/fixtures/certs/server-cert.pem new file mode 100644 index 00000000000..eefc08d7c04 --- /dev/null +++ b/tests/fixtures/certs/server-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLjCCAhagAwIBAgIUeaIyHLsYIv+B4URfEjrWzO4PeiswDQYJKoZIhvcNAQEL +BQAwFzEVMBMGA1UEAwwMbmF0cy10ZXN0LWNhMCAXDTI2MDgxMTExMTQzOFoYDzIx +MjYwNzE4MTExNDM4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDHjdlPKZGVaVk3i3d+r3vxciLPJI33nSt30dV8 +vLOrobmqSrgBWOtEOjqLcmZ+F/DWcFPb+/xCzmOIjUPS5GgxZWqiAvFKRPNvtDlP +T+d4+MIONbnj39iMYa+0w5vnHhH07FYXqLET9/1pua29+iHQULSFPcA/yNVGLITG +nHEIHnPD18ku25ekt9jEzZ+IJvGPSuV6P0LMbFvj4k8F1LI5A0rJwNC/ROhKYLlT +CjF9Mp1hTunxAzNQ/kdFdXRcTxk1q84/jp3pIFFMolYqXRHohc+xDzH6QQ8YJ9uT +x5pBj9HXXvwQiUP3hhCfBklTME/xwrZwo3EwY7yNGUfAcx6jAgMBAAGjczBxMBoG +A1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATATBgNVHSUEDDAKBggrBgEFBQcDATAd +BgNVHQ4EFgQUYGODh+L1Ax1Xl8e4rkHz/tJ5JtUwHwYDVR0jBBgwFoAU6W6JWRqK +cWtOnLL74XEWsps5gzcwDQYJKoZIhvcNAQELBQADggEBALQsDa4B9evIZMKpCvP3 +Gfe/iyrzWXuDVvkFUe0IAIVKP075diRTVzlI/nzu4urRxEE4G/NGsASi5AsLrS6Q +TQZc/A8nK7lCGS2pX9HLwb7QLZptdCSQoowm1IYZygv9oTMEKB2nEzFYu/FXQzkN +mktTDC1eGmGNuHKAjuffRvKcrFR+LJryPcAUy9hVS59tQd1ZflwoeOnsXGljYFbw +0aE2ov8TGy+vmB0zJswFZ9Kqxtrj7GeJ4spC+Sfy7w+ASurz33SdlLi5CLNlBeqy +I3I/Hcm7di0nxNCnh1nFfdRsggRELrMO42UXTnR++tjUWntxNxdgStnlt7FAhteR +t+o= +-----END CERTIFICATE----- diff --git a/tests/fixtures/certs/server-key.pem b/tests/fixtures/certs/server-key.pem new file mode 100644 index 00000000000..70892854586 --- /dev/null +++ b/tests/fixtures/certs/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDHjdlPKZGVaVk3 +i3d+r3vxciLPJI33nSt30dV8vLOrobmqSrgBWOtEOjqLcmZ+F/DWcFPb+/xCzmOI +jUPS5GgxZWqiAvFKRPNvtDlPT+d4+MIONbnj39iMYa+0w5vnHhH07FYXqLET9/1p +ua29+iHQULSFPcA/yNVGLITGnHEIHnPD18ku25ekt9jEzZ+IJvGPSuV6P0LMbFvj +4k8F1LI5A0rJwNC/ROhKYLlTCjF9Mp1hTunxAzNQ/kdFdXRcTxk1q84/jp3pIFFM +olYqXRHohc+xDzH6QQ8YJ9uTx5pBj9HXXvwQiUP3hhCfBklTME/xwrZwo3EwY7yN +GUfAcx6jAgMBAAECggEAAPRfQT8wKJhP6nGa2TrKq32ByB7jJPeWASU6UbXvYBdO +dY+U8MC1o2Zp6EfcUKcZqlTwOcGrU8B7/me6ltQr4WycRXkhIVtSFMs2aPg3Yb9C +4mGEaaAk/4rqFcHHyaO9rz/AK63NZwyNJEfUmm+LYN0599czR7rUtli5K+AMtF66 +v61ETN3JECJCEMsDM1UhiOBTMK+R8fDSA0ozlRzx4QFhHOe6qRWQX3bwDcrSw1sM +f99VGoq7jnGuJB31LGYA2gj07/HmFnG2smffo7jS05RY4RAbmStd7In3fFULcrfZ +UDdWbB4Vi0nefSwYB+vKBaiqIhbAPgHluLBHZeh2TQKBgQD52p2pDfR64CKxq0d/ +nRJxNWgyacpMAdARW3ilJPtBf6xHwIxYnPcClXpvWKWb/YKPoA9lWqbLq9xQWLkO +i70B0h2AhpH+BW95mRR5YxPO8sCp4pY1eGdttGSYVUGxTzcp+A+WUO3SE0RYnL0t +YPhfsb6MHSLRtQoELcN6cdgkvQKBgQDMdnvhJIHpzBP4Gq9eGvztxJCUBnH/w9xu +X2iK/9t9A3A6vWQvnDNkqZ87F3tnNypAcpWv+BOl+Ds1jyfzM6RCov9sbMUR7nf5 +fgKY7HiVg7bkJQqdjoke0Ay8Q2XVis438mBuxIBdlLXbFqHzg09B84NQf7FZC101 +PXJPzux23wKBgCbrwD2Sd/AgNCdbGBMwA6xw9l9MDYBTk/bwOdX38c/dNJgkzXgM +U4oqgN0RgRLQpeL7uwo92dfGWNaHS6SRbgr3kTLxQ5K/hAL8Lq1OGsrVE+Ai9OU1 +cV8sh7MXLNmTDnPSOZNf3fwoli/7kpicD3TQum8XJtJmEj5eZqIPU+09AoGAby8H +Elk95Bs7yLEKKAxY+hrn2/bdSw4A8mwjd2LGwmZ6Qxb2QgWY1OaAVivgyTHwYfdt +erFWTex8lhVwzgmJC0uDxjwntiaNSzjAxNPihruHWuzhpuf5nK3aHw4vdD31Aetl +kRZ+D2VstuTJ0JfZI639/GpT0G4jk24EmW2WnNUCgYBW/7TbSYEUhq4fHG8RBLli +lhznY1agzTVBu/V9uU1iQpUJ4ZN3+XlLNyakzZ72Lp+6XfYplgFGQqy8P+rsU72o +IbZvxkfgbLwpZQ95yMgThpYAJAMDVjNaEkDDwHGX0vbOdmPQxuBaqqsEkMDwXKJ3 +zt+/r86x0UCZsz39IIfK9A== +-----END PRIVATE KEY----- diff --git a/tests/fixtures/nats-tls.conf b/tests/fixtures/nats-tls.conf new file mode 100644 index 00000000000..16e8e1d44c5 --- /dev/null +++ b/tests/fixtures/nats-tls.conf @@ -0,0 +1,14 @@ +# NATS server with mutual TLS, used by the TLS e2e tests. +jetstream {} +http: 8222 + +tls { + cert_file: "/certs/server-cert.pem" + key_file: "/certs/server-key.pem" + ca_file: "/certs/ca.pem" + verify: true + timeout: 5 + # TLS-first: negotiate TLS at connect (matches the client's tls:// path) instead of + # the plaintext-INFO-then-STARTTLS-upgrade default, which the stream transport can't do. + handshake_first: true +} diff --git a/tests/fixtures/nats-ws.conf b/tests/fixtures/nats-ws.conf new file mode 100644 index 00000000000..6be50e922ec --- /dev/null +++ b/tests/fixtures/nats-ws.conf @@ -0,0 +1,8 @@ +# NATS server with WebSocket enabled, used by the WebSocket transport e2e test. +jetstream {} +http: 8222 + +websocket { + port: 8080 + no_tls: true +} From d275ce3e703ec8c19b13a69efd69049140dcfe5b Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:50 +0530 Subject: [PATCH 357/470] feat(sliding-window): add storage-agnostic SlidingWindow adapter contract --- src/Abuse/Adapters/SlidingWindow.php | 105 +++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow.php diff --git a/src/Abuse/Adapters/SlidingWindow.php b/src/Abuse/Adapters/SlidingWindow.php new file mode 100644 index 00000000000..6c6712ec19b --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow.php @@ -0,0 +1,105 @@ +limit - ($this->count($this->parseKey(), $this->timestamp) + 1); + + return (0 > $left) ? 0 : $left; + } + + /** + * Limit + * + * Return the limit integer + * + * @return int + */ + public function limit(): int + { + return $this->limit; + } + + /** + * Time + * + * Return the timestamp + * + * @return int + */ + public function time(): int + { + return $this->timestamp; + } + + /** + * Reset + * + * Clear the counters for the current key so the limit starts fresh. + * Implementations must clear both the current and previous window buckets. + * + * @return void + * + * @throws \Exception + */ + abstract public function reset(): void; +} From 0dbc1f055a61cedcb975c570168e6c38e91a62e4 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:50 +0530 Subject: [PATCH 358/470] feat(sliding-window): add RedisBase with atomic Lua check-and-increment --- .../Adapters/SlidingWindow/RedisBase.php | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow/RedisBase.php diff --git a/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/src/Abuse/Adapters/SlidingWindow/RedisBase.php new file mode 100644 index 00000000000..c10f066553e --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow/RedisBase.php @@ -0,0 +1,217 @@ += max_requests then + return { 0, 0, math.floor(current_count) } + end + + local new_count = redis.call('INCR', current_key) + redis.call('EXPIRE', current_key, ttl) + + local new_estimate = weighted_prev + new_count + local remaining = math.max(0, math.floor(max_requests - new_estimate)) + return { 1, remaining, new_count } + LUA; + + /** + * @var int + */ + protected int $windowSize; + + /** + * @var int + */ + protected int $ttl; + + /** + * @var float + */ + protected float $elapsed; + + /** + * Run the atomic check-and-increment script against the storage backend. + * + * @param list $keys current + previous bucket keys + * @param list $argv max_requests, elapsed, ttl + * @return array{0:int,1:int,2:int} { allowed, remaining, current_count } + */ + abstract protected function evaluateLimit(array $keys, array $argv): array; + + /** + * Read a bucket counter as an int (missing key => 0). + * + * @param string $key + * @return int + */ + abstract protected function bucketCount(string $key): int; + + /** + * Delete the given bucket keys. + * + * @param string ...$keys + * @return void + */ + abstract protected function deleteBuckets(string ...$keys): void; + + /** + * Initialise the window boundaries from the configured window size and ttl. + * Both `timestamp` (window start) and `elapsed` are derived from a single + * `now` so they stay consistent with the bucket being written. + * + * @param int $windowSize + * @param int $ttl + * @return void + */ + protected function initWindow(int $windowSize, int $ttl): void + { + if ($ttl < $windowSize) { + throw new \InvalidArgumentException('ttl must be greater than or equal to windowSize'); + } + + $now = \time(); + $this->windowSize = $windowSize; + $this->ttl = $ttl; + $this->timestamp = (int)($now - ($now % $windowSize)); // start of the current window + $this->elapsed = ($now - $this->timestamp) / $windowSize; // always in [0,1) + } + + /** + * Build a bucket key. The hash tag around $key forces the current and previous + * window buckets into the same cluster slot, so the multi-key Lua script and + * reset() do not raise CROSSSLOT on a Redis Cluster (harmless on single Redis). + * + * @param string $key + * @param int $timestamp + * @return string + */ + protected function bucketKey(string $key, int $timestamp): string + { + return self::NAMESPACE . '__{' . $key . '}__' . $timestamp; + } + + /** + * Check + * + * @return bool + * + * @throws \Throwable + */ + public function check(): bool + { + if ($this->limit === 0) { + return false; + } + + $key = $this->parseKey(); + + $result = $this->evaluateLimit( + [ + $this->bucketKey($key, $this->timestamp), // KEYS[1] current bucket + $this->bucketKey($key, $this->timestamp - $this->windowSize), // KEYS[2] previous bucket + ], + [ + $this->limit, // ARGV[1] max_requests + $this->elapsed, // ARGV[2] elapsed fraction + $this->ttl, // ARGV[3] ttl seconds + ], + ); + + [$allowed, , $count] = $result; + $this->count = $count; + + return $allowed === 0; + } + + /** + * Count + * + * Read-only weighted estimate of hits in the current sliding window + * (current bucket + weighted previous bucket). Used by remaining(). + * + * @param string $key + * @param int $timestamp + * @return int + */ + protected function count(string $key, int $timestamp): int + { + if (0 == $this->limit) { + return 0; + } + + if (! \is_null($this->count)) { + return $this->count; + } + + $current = $this->bucketCount($this->bucketKey($key, $timestamp)); + $previous = $this->bucketCount($this->bucketKey($key, $timestamp - $this->windowSize)); + + $this->count = (int) \floor($current + $previous * (1 - $this->elapsed)); + + return $this->count; + } + + /** + * Reset + * + * Clear both the current and previous window buckets so the limit starts fresh. + * + * @return void + */ + public function reset(): void + { + $key = $this->parseKey(); + + $this->deleteBuckets( + $this->bucketKey($key, $this->timestamp), + $this->bucketKey($key, $this->timestamp - $this->windowSize), + ); + + $this->count = 0; + } + + /** + * No need for manual cleanup - Redis TTL handles this automatically + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } +} From f28109173175d4c9d4e1ea253baab18b04450420 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:50 +0530 Subject: [PATCH 359/470] feat(sliding-window): add Redis adapter --- src/Abuse/Adapters/SlidingWindow/Redis.php | 98 ++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow/Redis.php diff --git a/src/Abuse/Adapters/SlidingWindow/Redis.php b/src/Abuse/Adapters/SlidingWindow/Redis.php new file mode 100644 index 00000000000..b667a85915d --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow/Redis.php @@ -0,0 +1,98 @@ += $windowSize so the + * previous window's bucket survives long enough to be weighted + * @param \Redis $redis Redis connection used for storage + */ + public function __construct(protected string $key, protected int $limit, int $windowSize, int $ttl, protected \Redis $redis) + { + $this->initWindow($windowSize, $ttl); + } + + /** + * @param list $keys + * @param list $argv + * @return array{0:int,1:int,2:int} + * + * @throws \RedisException + */ + protected function evaluateLimit(array $keys, array $argv): array + { + /** @var array{0:int,1:int,2:int} $result */ + $result = $this->redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); + + return $result; + } + + /** + * @param string $key + * @return int + * + * @throws \RedisException + */ + protected function bucketCount(string $key): int + { + $raw = $this->redis->get($key); + + return \is_numeric($raw) ? (int) $raw : 0; + } + + /** + * @param string ...$keys + * @return void + * + * @throws \RedisException + */ + protected function deleteBuckets(string ...$keys): void + { + $this->redis->del(...$keys); + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + + $cursor = null; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + do { + $keys = $this->redis->scan($cursor, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $this->redis->get($key); + } + + return $logs; + } +} From d68199f2a74effc550e35a8e79553fa8ad760a99 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:59 +0530 Subject: [PATCH 360/470] feat(sliding-window): add RedisCluster adapter with hash-tagged keys --- .../Adapters/SlidingWindow/RedisCluster.php | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow/RedisCluster.php diff --git a/src/Abuse/Adapters/SlidingWindow/RedisCluster.php b/src/Abuse/Adapters/SlidingWindow/RedisCluster.php new file mode 100644 index 00000000000..e15371e14d0 --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow/RedisCluster.php @@ -0,0 +1,95 @@ += $windowSize so the + * previous window's bucket survives long enough to be weighted + * @param \RedisCluster $redis Redis Cluster connection used for storage + */ + public function __construct(protected string $key, protected int $limit, int $windowSize, int $ttl, protected \RedisCluster $redis) + { + $this->initWindow($windowSize, $ttl); + } + + /** + * @param list $keys + * @param list $argv + * @return array{0:int,1:int,2:int} + * + * @throws \RedisClusterException + */ + protected function evaluateLimit(array $keys, array $argv): array + { + /** @var array{0:int,1:int,2:int} $result */ + $result = $this->redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); + + return $result; + } + + /** + * @param string $key + * @return int + * + * @throws \RedisClusterException + */ + protected function bucketCount(string $key): int + { + $raw = $this->redis->get($key); + + return \is_numeric($raw) ? (int) $raw : 0; + } + + /** + * @param string ...$keys + * @return void + * + * @throws \RedisClusterException + */ + protected function deleteBuckets(string ...$keys): void + { + $this->redis->del(...$keys); + } + + /** + * Get abuse logs with cursor-based pagination across masters + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = 0, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + foreach ($this->redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $this->redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + $matches = array_merge($matches, $keys); + } + } while ($cursor > 0 && count($matches) < $offset + $limit); + } + + sort($matches); + $matches = array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $values = $this->redis->mget($matches); + + return array_combine($matches, $values); + } +} From 2d15f05922f0751c3f1993de3a07d10012fa7187 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:59 +0530 Subject: [PATCH 361/470] feat(sliding-window): add RedisPool adapter --- .../Adapters/SlidingWindow/RedisPool.php | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow/RedisPool.php diff --git a/src/Abuse/Adapters/SlidingWindow/RedisPool.php b/src/Abuse/Adapters/SlidingWindow/RedisPool.php new file mode 100644 index 00000000000..2159a28fc15 --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow/RedisPool.php @@ -0,0 +1,162 @@ += $windowSize so the + * previous window's bucket survives long enough to be weighted + * @param UtopiaPool<\Redis>|UtopiaPool<\RedisCluster> $pool Pool yielding a Redis or RedisCluster connection + */ + public function __construct( + protected string $key, + protected int $limit, + int $windowSize, + int $ttl, + protected UtopiaPool $pool + ) { + $this->initWindow($windowSize, $ttl); + } + + /** + * @param list $keys + * @param list $argv + * @return array{0:int,1:int,2:int} + */ + protected function evaluateLimit(array $keys, array $argv): array + { + /** @var array{0:int,1:int,2:int} $result */ + $result = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($keys, $argv): array { + /** @var array{0:int,1:int,2:int} $result */ + $result = $redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); + + return $result; + }); + + return $result; + } + + /** + * @param string $key + * @return int + */ + protected function bucketCount(string $key): int + { + /** @var int $value */ + $value = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($key): int { + $raw = $redis->get($key); + + return \is_numeric($raw) ? (int) $raw : 0; + }); + + return $value; + } + + /** + * @param string ...$keys + * @return void + */ + protected function deleteBuckets(string ...$keys): void + { + $this->pool->use(function (\Redis|\RedisCluster $redis) use ($keys): void { + $redis->del(...$keys); + }); + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + + /** @var array $result */ + $result = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($offset, $limit): array { + if ($redis instanceof \RedisCluster) { + return $this->getRedisClusterLogs($redis, $offset, $limit); + } + + $cursor = null; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + do { + $keys = $redis->scan($cursor, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $redis->get($key); + } + + return $logs; + }); + + return $result; + } + + /** + * @param \RedisCluster $redis + * @param int $offset + * @param int $limit + * @return array + */ + private function getRedisClusterLogs(\RedisCluster $redis, int $offset, int $limit): array + { + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + foreach ($redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + } + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $values = $redis->mget($matches); + if (!\is_array($values)) { + return []; + } + + $logs = \array_combine($matches, $values); + if (!\is_array($logs)) { + return []; + } + + return $logs; + } +} From 5daae51a376fb55b8275c31587aed9bdf216b9f3 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:59 +0530 Subject: [PATCH 362/470] test(sliding-window): add shared adapter test base --- tests/Abuse/SlidingWindow/Base.php | 169 +++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 tests/Abuse/SlidingWindow/Base.php diff --git a/tests/Abuse/SlidingWindow/Base.php b/tests/Abuse/SlidingWindow/Base.php new file mode 100644 index 00000000000..57819937f22 --- /dev/null +++ b/tests/Abuse/SlidingWindow/Base.php @@ -0,0 +1,169 @@ +getAdapter('sw-static-key', 2, 1, 2); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test a dynamic key with a limit of 2 requests per window + */ + public function testDynamicKey(): void + { + $adapter = $this->getAdapter('sw-dynamic-key-{{ip}}', 2, 1, 2); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test a dynamic key with 2 params + */ + public function testDynamicKeyWith2Params(): void + { + $adapter = $this->getAdapter('sw-two-params-{{ip}}-{{email}}', 2, 1, 2); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $adapter->setParam('{{email}}', 'test@test.com'); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test a higher request rate like 10 requests per window + */ + public function testFastRequests(): void + { + $adapter = $this->getAdapter('sw-fast-requests-{{ip}}', 10, 1, 2); + $adapter->setParam('{{ip}}', '0.0.0.11'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 10; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + } + + /** + * Test that remaining reports the correct number of allowed requests + */ + public function testRemaining(): void + { + $adapter = $this->getAdapter('sw-remaining-{{ip}}', 3, 60, 120); + $adapter->setParam('{{ip}}', '0.0.0.12'); + $abuse = new Abuse($adapter); + + $this->assertSame(2, $adapter->remaining()); // nothing counted yet: limit - (0 + 1) + $this->assertSame(false, $abuse->check()); // 1 used + $this->assertSame(1, $adapter->remaining()); + $this->assertSame(false, $abuse->check()); // 2 used + $this->assertSame(0, $adapter->remaining()); + } + + /** + * Test that the window resets once both buckets expire + */ + public function testWindowExpiry(): void + { + $adapter = $this->getAdapter('sw-window-expiry-{{ip}}', 3, 1, 2); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 3; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + + // Wait for both the current and previous buckets (ttl = 2) to expire + sleep(3); + + // A fresh adapter recomputes the window; the old buckets are gone + $adapter = $this->getAdapter('sw-window-expiry-{{ip}}', 3, 1, 2); + $adapter->setParam('{{ip}}', '127.0.0.1'); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + } + + /** + * Verify that time() returns the aligned window start as an int + */ + public function testTimeFormat(): void + { + $windowSize = 1; + $now = \time(); + $adapter = $this->getAdapter('sw-time', 1, $windowSize, 2); + $this->assertSame((int)($now - ($now % $windowSize)), $adapter->time()); + $this->assertSame(true, \is_int($adapter->time())); + } + + /** + * Test the reset functionality clears both buckets + */ + public function testReset(): void + { + $adapter = $this->getAdapter('sw-reset-test-{{ip}}', 5, 600, 1200); + $adapter->setParam('{{ip}}', '192.168.1.1'); + $abuse = new Abuse($adapter); + + // 5 OK, 6th limited + for ($i = 0; $i < 5; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + + // Reset clears the counters + $abuse->reset(); + + // 5 more OK, then limited again + for ($i = 0; $i < 5; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + } + + /** + * Test that a ttl smaller than the window size is rejected + */ + public function testTtlGuard(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->getAdapter('sw-guard', 1, 10, 5); + } + + /** + * Test that limit 0 means unlimited + */ + public function testUnlimited(): void + { + $adapter = $this->getAdapter('sw-unlimited', 0, 1, 2); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 20; $i++) { + $this->assertSame(false, $abuse->check()); + } + } +} From 4d8c86bd0d9e0143d023a70f14f0fe5a3d21ec21 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:37:59 +0530 Subject: [PATCH 363/470] test(sliding-window): add Redis, RedisCluster and RedisPool test cases --- .../Abuse/SlidingWindow/RedisClusterTest.php | 45 ++++++++++++++++ tests/Abuse/SlidingWindow/RedisPoolTest.php | 53 +++++++++++++++++++ tests/Abuse/SlidingWindow/RedisTest.php | 43 +++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 tests/Abuse/SlidingWindow/RedisClusterTest.php create mode 100644 tests/Abuse/SlidingWindow/RedisPoolTest.php create mode 100644 tests/Abuse/SlidingWindow/RedisTest.php diff --git a/tests/Abuse/SlidingWindow/RedisClusterTest.php b/tests/Abuse/SlidingWindow/RedisClusterTest.php new file mode 100644 index 00000000000..cfaa8f0d274 --- /dev/null +++ b/tests/Abuse/SlidingWindow/RedisClusterTest.php @@ -0,0 +1,45 @@ +close(); + } + } +} diff --git a/tests/Abuse/SlidingWindow/RedisPoolTest.php b/tests/Abuse/SlidingWindow/RedisPoolTest.php new file mode 100644 index 00000000000..20dbb8ea304 --- /dev/null +++ b/tests/Abuse/SlidingWindow/RedisPoolTest.php @@ -0,0 +1,53 @@ +|null + */ + protected static ?Pool $pool = null; + + public static function setUpBeforeClass(): void + { + if (isset(self::$pool)) { + return; + } + + self::$pool = new Pool(new Stack(), 'abuse-sw-redis', 2, function (): \Redis { + $redis = new \Redis(); + $redis->connect('redis', 6379); + + return $redis; + }, timeout: 0.0); + } + + public function getAdapter(string $key, int $limit, int $windowSize, int $ttl): SlidingWindow + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + /** @var Pool<\Redis> $pool */ + return new AdapterRedisPool('sw-pool-' . $key, $limit, $windowSize, $ttl, $pool); + } + + public static function tearDownAfterClass(): void + { + if (!isset(self::$pool)) { + return; + } + + self::$pool->use(function (mixed $redis): void { + if ($redis instanceof \Redis) { + $redis->close(); + } + }); + self::$pool = null; + } +} diff --git a/tests/Abuse/SlidingWindow/RedisTest.php b/tests/Abuse/SlidingWindow/RedisTest.php new file mode 100644 index 00000000000..126cea22bc2 --- /dev/null +++ b/tests/Abuse/SlidingWindow/RedisTest.php @@ -0,0 +1,43 @@ +connect('redis', 6379); + + return $redis; + } + + public function getAdapter(string $key, int $limit, int $windowSize, int $ttl): SlidingWindow + { + return new AdapterRedis($key, $limit, $windowSize, $ttl, self::$redis); + } + + public static function tearDownAfterClass(): void + { + if (isset(self::$redis)) { + self::$redis->close(); + } + } +} From 5c016a824f39f6bf68fcfc1780228a58ad5bd91e Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:51:38 +0530 Subject: [PATCH 364/470] refactor(sliding-window): reduce adapter seams to eval/get/delete primitives --- src/Abuse/Adapters/SlidingWindow/Redis.php | 20 ++++------ .../Adapters/SlidingWindow/RedisBase.php | 37 +++++++++++-------- .../Adapters/SlidingWindow/RedisCluster.php | 20 ++++------ .../Adapters/SlidingWindow/RedisPool.php | 30 ++++----------- 4 files changed, 45 insertions(+), 62 deletions(-) diff --git a/src/Abuse/Adapters/SlidingWindow/Redis.php b/src/Abuse/Adapters/SlidingWindow/Redis.php index b667a85915d..af22065c7ed 100644 --- a/src/Abuse/Adapters/SlidingWindow/Redis.php +++ b/src/Abuse/Adapters/SlidingWindow/Redis.php @@ -18,31 +18,27 @@ public function __construct(protected string $key, protected int $limit, int $wi } /** + * @param string $script * @param list $keys * @param list $argv - * @return array{0:int,1:int,2:int} + * @return mixed * * @throws \RedisException */ - protected function evaluateLimit(array $keys, array $argv): array + protected function eval(string $script, array $keys, array $argv): mixed { - /** @var array{0:int,1:int,2:int} $result */ - $result = $this->redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); - - return $result; + return $this->redis->eval($script, [...$keys, ...$argv], \count($keys)); } /** * @param string $key - * @return int + * @return mixed * * @throws \RedisException */ - protected function bucketCount(string $key): int + protected function get(string $key): mixed { - $raw = $this->redis->get($key); - - return \is_numeric($raw) ? (int) $raw : 0; + return $this->redis->get($key); } /** @@ -51,7 +47,7 @@ protected function bucketCount(string $key): int * * @throws \RedisException */ - protected function deleteBuckets(string ...$keys): void + protected function delete(string ...$keys): void { $this->redis->del(...$keys); } diff --git a/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/src/Abuse/Adapters/SlidingWindow/RedisBase.php index c10f066553e..aa3ef08d137 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisBase.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisBase.php @@ -8,8 +8,7 @@ * Shared implementation for the Redis-family sliding-window adapters * (Redis, RedisCluster, RedisPool). Owns the atomic Lua script, the window * math and the check/count/reset flow. Subclasses only implement how they - * talk to storage via the evaluateLimit()/bucketCount()/deleteBuckets() seams, plus - * their own getLogs(). + * talk to storage via the eval()/get()/delete() seams, plus their own getLogs(). */ abstract class RedisBase extends SlidingWindow { @@ -64,29 +63,30 @@ abstract class RedisBase extends SlidingWindow protected float $elapsed; /** - * Run the atomic check-and-increment script against the storage backend. + * Run a Lua script against the storage backend. * - * @param list $keys current + previous bucket keys - * @param list $argv max_requests, elapsed, ttl - * @return array{0:int,1:int,2:int} { allowed, remaining, current_count } + * @param string $script + * @param list $keys + * @param list $argv + * @return mixed the raw script result */ - abstract protected function evaluateLimit(array $keys, array $argv): array; + abstract protected function eval(string $script, array $keys, array $argv): mixed; /** - * Read a bucket counter as an int (missing key => 0). + * Get the raw value stored at $key (null/false when missing). * * @param string $key - * @return int + * @return mixed */ - abstract protected function bucketCount(string $key): int; + abstract protected function get(string $key): mixed; /** - * Delete the given bucket keys. + * Delete the given keys. * * @param string ...$keys * @return void */ - abstract protected function deleteBuckets(string ...$keys): void; + abstract protected function delete(string ...$keys): void; /** * Initialise the window boundaries from the configured window size and ttl. @@ -139,7 +139,9 @@ public function check(): bool $key = $this->parseKey(); - $result = $this->evaluateLimit( + /** @var array{0:int,1:int,2:int} $result */ + $result = $this->eval( + self::LIMIT_CHECK_SCRIPT, [ $this->bucketKey($key, $this->timestamp), // KEYS[1] current bucket $this->bucketKey($key, $this->timestamp - $this->windowSize), // KEYS[2] previous bucket @@ -177,8 +179,11 @@ protected function count(string $key, int $timestamp): int return $this->count; } - $current = $this->bucketCount($this->bucketKey($key, $timestamp)); - $previous = $this->bucketCount($this->bucketKey($key, $timestamp - $this->windowSize)); + $currentRaw = $this->get($this->bucketKey($key, $timestamp)); + $previousRaw = $this->get($this->bucketKey($key, $timestamp - $this->windowSize)); + + $current = \is_numeric($currentRaw) ? (int) $currentRaw : 0; + $previous = \is_numeric($previousRaw) ? (int) $previousRaw : 0; $this->count = (int) \floor($current + $previous * (1 - $this->elapsed)); @@ -196,7 +201,7 @@ public function reset(): void { $key = $this->parseKey(); - $this->deleteBuckets( + $this->delete( $this->bucketKey($key, $this->timestamp), $this->bucketKey($key, $this->timestamp - $this->windowSize), ); diff --git a/src/Abuse/Adapters/SlidingWindow/RedisCluster.php b/src/Abuse/Adapters/SlidingWindow/RedisCluster.php index e15371e14d0..6e519a8a6d4 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisCluster.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisCluster.php @@ -18,31 +18,27 @@ public function __construct(protected string $key, protected int $limit, int $wi } /** + * @param string $script * @param list $keys * @param list $argv - * @return array{0:int,1:int,2:int} + * @return mixed * * @throws \RedisClusterException */ - protected function evaluateLimit(array $keys, array $argv): array + protected function eval(string $script, array $keys, array $argv): mixed { - /** @var array{0:int,1:int,2:int} $result */ - $result = $this->redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); - - return $result; + return $this->redis->eval($script, [...$keys, ...$argv], \count($keys)); } /** * @param string $key - * @return int + * @return mixed * * @throws \RedisClusterException */ - protected function bucketCount(string $key): int + protected function get(string $key): mixed { - $raw = $this->redis->get($key); - - return \is_numeric($raw) ? (int) $raw : 0; + return $this->redis->get($key); } /** @@ -51,7 +47,7 @@ protected function bucketCount(string $key): int * * @throws \RedisClusterException */ - protected function deleteBuckets(string ...$keys): void + protected function delete(string ...$keys): void { $this->redis->del(...$keys); } diff --git a/src/Abuse/Adapters/SlidingWindow/RedisPool.php b/src/Abuse/Adapters/SlidingWindow/RedisPool.php index 2159a28fc15..fd0729a7bba 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisPool.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisPool.php @@ -25,44 +25,30 @@ public function __construct( } /** + * @param string $script * @param list $keys * @param list $argv - * @return array{0:int,1:int,2:int} + * @return mixed */ - protected function evaluateLimit(array $keys, array $argv): array + protected function eval(string $script, array $keys, array $argv): mixed { - /** @var array{0:int,1:int,2:int} $result */ - $result = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($keys, $argv): array { - /** @var array{0:int,1:int,2:int} $result */ - $result = $redis->eval(self::LIMIT_CHECK_SCRIPT, [...$keys, ...$argv], \count($keys)); - - return $result; - }); - - return $result; + return $this->pool->use(fn (\Redis|\RedisCluster $redis): mixed => $redis->eval($script, [...$keys, ...$argv], \count($keys))); } /** * @param string $key - * @return int + * @return mixed */ - protected function bucketCount(string $key): int + protected function get(string $key): mixed { - /** @var int $value */ - $value = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($key): int { - $raw = $redis->get($key); - - return \is_numeric($raw) ? (int) $raw : 0; - }); - - return $value; + return $this->pool->use(fn (\Redis|\RedisCluster $redis): mixed => $redis->get($key)); } /** * @param string ...$keys * @return void */ - protected function deleteBuckets(string ...$keys): void + protected function delete(string ...$keys): void { $this->pool->use(function (\Redis|\RedisCluster $redis) use ($keys): void { $redis->del(...$keys); From bf8336dd410e1e6c8cf310ec2118d80c4c7157e0 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 18:55:29 +0530 Subject: [PATCH 365/470] fix(sliding-window): validate window/ttl bounds and cache weighted estimate - reject windowSize <= 0 to avoid DivisionByZeroError - require ttl >= 2*windowSize so the previous bucket outlives the current window - return and cache the weighted estimate so remaining() stays consistent with check() --- .../Adapters/SlidingWindow/RedisBase.php | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/src/Abuse/Adapters/SlidingWindow/RedisBase.php index aa3ef08d137..83c66b4a2f9 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisBase.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisBase.php @@ -20,7 +20,8 @@ abstract class RedisBase extends SlidingWindow * KEYS[1] current bucket, KEYS[2] previous bucket. * ARGV[1] max_requests, ARGV[2] elapsed fraction of current window [0,1), ARGV[3] ttl seconds. * - * Returns { allowed (0|1), remaining, current_count }. + * Returns { allowed (0|1), remaining, estimate } where estimate is the + * weighted sliding-window count (current bucket + weighted previous bucket). */ protected const string LIMIT_CHECK_SCRIPT = <<<'LUA' local current_key = KEYS[1] @@ -36,7 +37,7 @@ abstract class RedisBase extends SlidingWindow local estimated = weighted_prev + current_count if estimated >= max_requests then - return { 0, 0, math.floor(current_count) } + return { 0, 0, math.floor(estimated) } end local new_count = redis.call('INCR', current_key) @@ -44,7 +45,7 @@ abstract class RedisBase extends SlidingWindow local new_estimate = weighted_prev + new_count local remaining = math.max(0, math.floor(max_requests - new_estimate)) - return { 1, remaining, new_count } + return { 1, remaining, math.floor(new_estimate) } LUA; /** @@ -93,14 +94,27 @@ abstract protected function delete(string ...$keys): void; * Both `timestamp` (window start) and `elapsed` are derived from a single * `now` so they stay consistent with the bucket being written. * + * The window is captured once, at construction. An adapter instance is meant + * to be short-lived (created per request); reusing one across a window + * boundary keeps operating on the construction-time window. This matches the + * TimeLimit adapters' behaviour - construct a fresh adapter per check. + * * @param int $windowSize * @param int $ttl * @return void */ protected function initWindow(int $windowSize, int $ttl): void { - if ($ttl < $windowSize) { - throw new \InvalidArgumentException('ttl must be greater than or equal to windowSize'); + if ($windowSize <= 0) { + throw new \InvalidArgumentException('windowSize must be greater than 0'); + } + + // The previous bucket keeps contributing (weighted) throughout the whole + // current window, and its ttl is set from its last write - which in the + // worst case is at the very start of its own window. It therefore needs to + // survive up to two full windows, so ttl must be >= 2 * windowSize. + if ($ttl < $windowSize * 2) { + throw new \InvalidArgumentException('ttl must be at least twice the windowSize so the previous window bucket outlives the current window'); } $now = \time(); @@ -153,8 +167,10 @@ public function check(): bool ], ); - [$allowed, , $count] = $result; - $this->count = $count; + // $estimate is the weighted sliding-window count, so a following + // remaining() call stays consistent with what check() decided on. + [$allowed, , $estimate] = $result; + $this->count = $estimate; return $allowed === 0; } From 25016c0d256d655e1124b6f82b1ff018152c5030 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 12 Aug 2026 19:08:02 +0530 Subject: [PATCH 366/470] refactor(sliding-window): improve window computation and state management --- .../Adapters/SlidingWindow/RedisBase.php | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/src/Abuse/Adapters/SlidingWindow/RedisBase.php index 83c66b4a2f9..ade19adb4d8 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisBase.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisBase.php @@ -9,6 +9,10 @@ * (Redis, RedisCluster, RedisPool). Owns the atomic Lua script, the window * math and the check/count/reset flow. Subclasses only implement how they * talk to storage via the eval()/get()/delete() seams, plus their own getLogs(). + * + * The window (start timestamp + elapsed fraction) is recomputed from the clock + * on every operation, so an adapter instance stays correct even if it is reused + * across a window boundary. */ abstract class RedisBase extends SlidingWindow { @@ -59,9 +63,11 @@ abstract class RedisBase extends SlidingWindow protected int $ttl; /** - * @var float + * Window start the cached $count was computed for (null when no cache). + * + * @var int|null */ - protected float $elapsed; + protected ?int $countTimestamp = null; /** * Run a Lua script against the storage backend. @@ -90,14 +96,9 @@ abstract protected function get(string $key): mixed; abstract protected function delete(string ...$keys): void; /** - * Initialise the window boundaries from the configured window size and ttl. - * Both `timestamp` (window start) and `elapsed` are derived from a single - * `now` so they stay consistent with the bucket being written. - * - * The window is captured once, at construction. An adapter instance is meant - * to be short-lived (created per request); reusing one across a window - * boundary keeps operating on the construction-time window. This matches the - * TimeLimit adapters' behaviour - construct a fresh adapter per check. + * Validate and store the window configuration. The window itself is derived + * live in window(); here we only seed $timestamp so the inherited property is + * initialised before remaining()/time() read it. * * @param int $windowSize * @param int $ttl @@ -117,11 +118,22 @@ protected function initWindow(int $windowSize, int $ttl): void throw new \InvalidArgumentException('ttl must be at least twice the windowSize so the previous window bucket outlives the current window'); } - $now = \time(); $this->windowSize = $windowSize; $this->ttl = $ttl; - $this->timestamp = (int)($now - ($now % $windowSize)); // start of the current window - $this->elapsed = ($now - $this->timestamp) / $windowSize; // always in [0,1) + [$this->timestamp] = $this->window(); + } + + /** + * Compute the live window from the current time. + * + * @return array{0:int,1:float} [window start timestamp, elapsed fraction in [0,1)] + */ + private function window(): array + { + $now = \time(); + $timestamp = (int)($now - ($now % $this->windowSize)); // start of the current window + + return [$timestamp, ($now - $timestamp) / $this->windowSize]; } /** @@ -138,6 +150,20 @@ protected function bucketKey(string $key, int $timestamp): string return self::NAMESPACE . '__{' . $key . '}__' . $timestamp; } + /** + * Time + * + * Start timestamp of the current window, recomputed from the clock. + * + * @return int + */ + public function time(): int + { + [$this->timestamp] = $this->window(); + + return $this->timestamp; + } + /** * Check * @@ -152,18 +178,20 @@ public function check(): bool } $key = $this->parseKey(); + [$timestamp, $elapsed] = $this->window(); + $this->timestamp = $timestamp; /** @var array{0:int,1:int,2:int} $result */ $result = $this->eval( self::LIMIT_CHECK_SCRIPT, [ - $this->bucketKey($key, $this->timestamp), // KEYS[1] current bucket - $this->bucketKey($key, $this->timestamp - $this->windowSize), // KEYS[2] previous bucket + $this->bucketKey($key, $timestamp), // KEYS[1] current bucket + $this->bucketKey($key, $timestamp - $this->windowSize), // KEYS[2] previous bucket ], [ - $this->limit, // ARGV[1] max_requests - $this->elapsed, // ARGV[2] elapsed fraction - $this->ttl, // ARGV[3] ttl seconds + $this->limit, // ARGV[1] max_requests + $elapsed, // ARGV[2] elapsed fraction + $this->ttl, // ARGV[3] ttl seconds ], ); @@ -171,6 +199,7 @@ public function check(): bool // remaining() call stays consistent with what check() decided on. [$allowed, , $estimate] = $result; $this->count = $estimate; + $this->countTimestamp = $timestamp; return $allowed === 0; } @@ -191,17 +220,21 @@ protected function count(string $key, int $timestamp): int return 0; } - if (! \is_null($this->count)) { + [$windowStart, $elapsed] = $this->window(); + $this->timestamp = $windowStart; + + if ($this->count !== null && $this->countTimestamp === $windowStart) { return $this->count; } - $currentRaw = $this->get($this->bucketKey($key, $timestamp)); - $previousRaw = $this->get($this->bucketKey($key, $timestamp - $this->windowSize)); + $currentRaw = $this->get($this->bucketKey($key, $windowStart)); + $previousRaw = $this->get($this->bucketKey($key, $windowStart - $this->windowSize)); $current = \is_numeric($currentRaw) ? (int) $currentRaw : 0; $previous = \is_numeric($previousRaw) ? (int) $previousRaw : 0; - $this->count = (int) \floor($current + $previous * (1 - $this->elapsed)); + $this->count = (int) \floor($current + $previous * (1 - $elapsed)); + $this->countTimestamp = $windowStart; return $this->count; } @@ -216,13 +249,16 @@ protected function count(string $key, int $timestamp): int public function reset(): void { $key = $this->parseKey(); + [$windowStart] = $this->window(); + $this->timestamp = $windowStart; $this->delete( - $this->bucketKey($key, $this->timestamp), - $this->bucketKey($key, $this->timestamp - $this->windowSize), + $this->bucketKey($key, $windowStart), + $this->bucketKey($key, $windowStart - $this->windowSize), ); $this->count = 0; + $this->countTimestamp = $windowStart; } /** From 252a10dfd1919003f75681ee31357e23cc09e47b Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 11:43:01 +0530 Subject: [PATCH 367/470] feat(token-bucket): add agnostic adapter and Redis-family base Introduce the storage-agnostic TokenBucket adapter (mirrors SlidingWindow: count()/remaining()/limit()/time()) and RedisBase, which owns the atomic refill-and-consume Lua script, a read-only refill estimate and the eval()/delete() seams. Bucket state is a single Redis hash; ttl is derived from capacity/refillRate. --- src/Abuse/Adapters/TokenBucket.php | 107 +++++++++ src/Abuse/Adapters/TokenBucket/RedisBase.php | 239 +++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 src/Abuse/Adapters/TokenBucket.php create mode 100644 src/Abuse/Adapters/TokenBucket/RedisBase.php diff --git a/src/Abuse/Adapters/TokenBucket.php b/src/Abuse/Adapters/TokenBucket.php new file mode 100644 index 00000000000..899ce6e2c9e --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket.php @@ -0,0 +1,107 @@ +tokens - ($this->count($this->parseKey(), $this->timestamp) + 1); + + return (0 > $left) ? 0 : $left; + } + + /** + * Limit + * + * Return the bucket capacity + * + * @return int + */ + public function limit(): int + { + return $this->tokens; + } + + /** + * Time + * + * Return the timestamp + * + * @return int + */ + public function time(): int + { + return $this->timestamp; + } + + /** + * Reset + * + * Clear the bucket for the current key so it starts full again. + * + * @return void + * + * @throws \Exception + */ + abstract public function reset(): void; +} diff --git a/src/Abuse/Adapters/TokenBucket/RedisBase.php b/src/Abuse/Adapters/TokenBucket/RedisBase.php new file mode 100644 index 00000000000..fb633b83164 --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket/RedisBase.php @@ -0,0 +1,239 @@ += 1 then + tokens = tokens - 1 + allowed = 1 + end + + redis.call('HSET', key, 'tokens', tostring(tokens), 'last_refill', tostring(now)) + redis.call('EXPIRE', key, math.ceil(max_tokens / refill_rate) + 1) + + return { allowed, tostring(tokens) } + LUA; + + /** + * Read-only token estimate: refills the bucket for the elapsed time without + * consuming anything or writing back. Used by remaining(). + * + * KEYS[1] bucket hash key. + * ARGV[1] max_tokens, ARGV[2] refill_rate, ARGV[3] now. + * + * Returns the available token balance as a string. + */ + protected const string TOKENS_SCRIPT = <<<'LUA' + local key = KEYS[1] + local max_tokens = tonumber(ARGV[1]) + local refill_rate = tonumber(ARGV[2]) + local now = tonumber(ARGV[3]) + + local data = redis.call('HMGET', key, 'tokens', 'last_refill') + local tokens = tonumber(data[1]) or max_tokens + local last_refill = tonumber(data[2]) or now + + local elapsed = now - last_refill + if elapsed < 0 then elapsed = 0 end + tokens = math.min(max_tokens, tokens + elapsed * refill_rate) + + return tostring(tokens) + LUA; + + /** + * Tokens refilled per second. + * + * @var float + */ + protected float $refillRate; + + /** + * Run a Lua script against the storage backend. + * + * @param string $script + * @param list $keys + * @param list $argv + * @return mixed the raw script result + */ + abstract protected function eval(string $script, array $keys, array $argv): mixed; + + /** + * Delete the given keys. + * + * @param string ...$keys + * @return void + */ + abstract protected function delete(string ...$keys): void; + + /** + * Validate and store the bucket configuration. + * + * @param float $refillRate + * @return void + */ + protected function initBucket(float $refillRate): void + { + if ($refillRate <= 0) { + throw new \InvalidArgumentException('refillRate must be greater than 0'); + } + + $this->refillRate = $refillRate; + $this->timestamp = \time(); + } + + /** + * Build the bucket hash key for a given abuse key. + * + * @param string $key + * @return string + */ + protected function bucketKey(string $key): string + { + return self::NAMESPACE . '__' . $key; + } + + /** + * Check + * + * @return bool + * + * @throws \Throwable + */ + public function check(): bool + { + if ($this->tokens === 0) { + return false; + } + + $key = $this->parseKey(); + $this->timestamp = \time(); + + /** @var array{0:int,1:string} $result */ + $result = $this->eval( + self::LIMIT_CHECK_SCRIPT, + [ + $this->bucketKey($key), // KEYS[1] bucket hash + ], + [ + $this->tokens, // ARGV[1] max_tokens + $this->refillRate, // ARGV[2] refill_rate + \microtime(true), // ARGV[3] now (fractional seconds) + ], + ); + + // $available is the token balance left after consuming; store the + // consumed count so a following remaining() stays consistent with it. + [$allowed, $available] = $result; + $balance = \is_numeric($available) ? (float) $available : 0.0; + $this->count = (int) \floor($this->tokens - $balance); + + return (int) $allowed === 0; + } + + /** + * Count + * + * Read-only estimate of the tokens already consumed from the bucket + * (capacity minus the tokens available after refilling). Used by remaining(). + * Reuses the value recorded by the most recent check()/reset() this request; + * otherwise reads a fresh estimate from storage. + * + * @param string $key + * @param int $timestamp + * @return int + */ + protected function count(string $key, int $timestamp): int + { + if ($this->tokens === 0) { + return 0; + } + + $this->timestamp = \time(); + + if ($this->count !== null) { + return $this->count; + } + + $raw = $this->eval( + self::TOKENS_SCRIPT, + [ + $this->bucketKey($key), + ], + [ + $this->tokens, + $this->refillRate, + \microtime(true), + ], + ); + + $balance = \is_numeric($raw) ? (float) $raw : (float) $this->tokens; + $this->count = (int) \floor($this->tokens - $balance); + + return $this->count; + } + + /** + * Reset + * + * Drop the bucket state so the next request sees a full bucket. + * + * @return void + */ + public function reset(): void + { + $this->delete($this->bucketKey($this->parseKey())); + + $this->count = 0; + } + + /** + * No need for manual cleanup - Redis TTL handles this automatically + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } +} From 1830b617832666b77793b92f86c3460080e4feeb Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 11:43:10 +0530 Subject: [PATCH 368/470] feat(token-bucket): add Redis, RedisCluster and RedisPool adapters Concrete adapters implementing the eval()/delete() seams plus getLogs() (reads bucket hashes via hGetAll). Constructor takes (key, tokens, refillRate). --- src/Abuse/Adapters/TokenBucket/Redis.php | 81 +++++++++++ .../Adapters/TokenBucket/RedisCluster.php | 81 +++++++++++ src/Abuse/Adapters/TokenBucket/RedisPool.php | 131 ++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 src/Abuse/Adapters/TokenBucket/Redis.php create mode 100644 src/Abuse/Adapters/TokenBucket/RedisCluster.php create mode 100644 src/Abuse/Adapters/TokenBucket/RedisPool.php diff --git a/src/Abuse/Adapters/TokenBucket/Redis.php b/src/Abuse/Adapters/TokenBucket/Redis.php new file mode 100644 index 00000000000..58e24026d5e --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket/Redis.php @@ -0,0 +1,81 @@ +initBucket($refillRate); + } + + /** + * @param string $script + * @param list $keys + * @param list $argv + * @return mixed + * + * @throws \RedisException + */ + protected function eval(string $script, array $keys, array $argv): mixed + { + return $this->redis->eval($script, [...$keys, ...$argv], \count($keys)); + } + + /** + * @param string ...$keys + * @return void + * + * @throws \RedisException + */ + protected function delete(string ...$keys): void + { + $this->redis->del(...$keys); + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + + $cursor = null; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + do { + $keys = $this->redis->scan($cursor, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $this->redis->hGetAll($key); + } + + return $logs; + } +} diff --git a/src/Abuse/Adapters/TokenBucket/RedisCluster.php b/src/Abuse/Adapters/TokenBucket/RedisCluster.php new file mode 100644 index 00000000000..ced320c7ee4 --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket/RedisCluster.php @@ -0,0 +1,81 @@ +initBucket($refillRate); + } + + /** + * @param string $script + * @param list $keys + * @param list $argv + * @return mixed + * + * @throws \RedisClusterException + */ + protected function eval(string $script, array $keys, array $argv): mixed + { + return $this->redis->eval($script, [...$keys, ...$argv], \count($keys)); + } + + /** + * @param string ...$keys + * @return void + * + * @throws \RedisClusterException + */ + protected function delete(string ...$keys): void + { + $this->redis->del(...$keys); + } + + /** + * Get abuse logs with cursor-based pagination across masters + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = 0, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + foreach ($this->redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $this->redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + $matches = array_merge($matches, $keys); + } + } while ($cursor > 0 && count($matches) < $offset + $limit); + } + + sort($matches); + $matches = array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $this->redis->hGetAll($key); + } + + return $logs; + } +} diff --git a/src/Abuse/Adapters/TokenBucket/RedisPool.php b/src/Abuse/Adapters/TokenBucket/RedisPool.php new file mode 100644 index 00000000000..34cfec93c27 --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket/RedisPool.php @@ -0,0 +1,131 @@ +|UtopiaPool<\RedisCluster> $pool Pool yielding a Redis or RedisCluster connection + */ + public function __construct( + protected string $key, + protected int $tokens, + float $refillRate, + protected UtopiaPool $pool + ) { + $this->initBucket($refillRate); + } + + /** + * @param string $script + * @param list $keys + * @param list $argv + * @return mixed + */ + protected function eval(string $script, array $keys, array $argv): mixed + { + return $this->pool->use(fn (\Redis|\RedisCluster $redis): mixed => $redis->eval($script, [...$keys, ...$argv], \count($keys))); + } + + /** + * @param string ...$keys + * @return void + */ + protected function delete(string ...$keys): void + { + $this->pool->use(function (\Redis|\RedisCluster $redis) use ($keys): void { + $redis->del(...$keys); + }); + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + $offset = $offset ?? 0; + $limit = $limit ?? 25; + + /** @var array $result */ + $result = $this->pool->use(function (\Redis|\RedisCluster $redis) use ($offset, $limit): array { + if ($redis instanceof \RedisCluster) { + return $this->getRedisClusterLogs($redis, $offset, $limit); + } + + $cursor = null; + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + do { + $keys = $redis->scan($cursor, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $redis->hGetAll($key); + } + + return $logs; + }); + + return $result; + } + + /** + * @param \RedisCluster $redis + * @param int $offset + * @param int $limit + * @return array + */ + private function getRedisClusterLogs(\RedisCluster $redis, int $offset, int $limit): array + { + $matches = []; + $pattern = self::NAMESPACE . '__*'; + + foreach ($redis->_masters() as $master) { + $cursor = null; + do { + /** @phpstan-ignore-next-line */ + $keys = $redis->scan($cursor, $master, $pattern, 100); + if ($keys !== false) { + \array_push($matches, ...$keys); + } + } while ($cursor > 0); + } + + \sort($matches); + $matches = \array_slice($matches, $offset, $limit); + + if (empty($matches)) { + return []; + } + + $logs = []; + foreach ($matches as $key) { + $logs[$key] = $redis->hGetAll($key); + } + + return $logs; + } +} From 84f4424310081e034ffafecb6c52bbecaa7b4945 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 11:43:10 +0530 Subject: [PATCH 369/470] test(token-bucket): add Redis, RedisCluster and RedisPool test cases Shared Base suite covering burst/capacity, remaining, refill over time, reset, refill-rate guard and unlimited (tokens 0). --- tests/Abuse/TokenBucket/Base.php | 160 +++++++++++++++++++ tests/Abuse/TokenBucket/RedisClusterTest.php | 45 ++++++ tests/Abuse/TokenBucket/RedisPoolTest.php | 53 ++++++ tests/Abuse/TokenBucket/RedisTest.php | 43 +++++ 4 files changed, 301 insertions(+) create mode 100644 tests/Abuse/TokenBucket/Base.php create mode 100644 tests/Abuse/TokenBucket/RedisClusterTest.php create mode 100644 tests/Abuse/TokenBucket/RedisPoolTest.php create mode 100644 tests/Abuse/TokenBucket/RedisTest.php diff --git a/tests/Abuse/TokenBucket/Base.php b/tests/Abuse/TokenBucket/Base.php new file mode 100644 index 00000000000..e9ce08bd163 --- /dev/null +++ b/tests/Abuse/TokenBucket/Base.php @@ -0,0 +1,160 @@ +getAdapter('tb-static-key', 2, 0.001); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test a dynamic key with a capacity of 2 tokens + */ + public function testDynamicKey(): void + { + $adapter = $this->getAdapter('tb-dynamic-key-{{ip}}', 2, 0.001); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test a dynamic key with 2 params + */ + public function testDynamicKeyWith2Params(): void + { + $adapter = $this->getAdapter('tb-two-params-{{ip}}-{{email}}', 2, 0.001); + $adapter->setParam('{{ip}}', '0.0.0.10'); + $adapter->setParam('{{email}}', 'test@test.com'); + $abuse = new Abuse($adapter); + $this->assertSame(false, $abuse->check()); + $this->assertSame(false, $abuse->check()); + $this->assertSame(true, $abuse->check()); + } + + /** + * Test that a full bucket allows a burst up to its capacity + */ + public function testBurst(): void + { + $adapter = $this->getAdapter('tb-burst-{{ip}}', 10, 0.001); + $adapter->setParam('{{ip}}', '0.0.0.11'); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 10; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + } + + /** + * Test that remaining reports the tokens still available + */ + public function testRemaining(): void + { + $adapter = $this->getAdapter('tb-remaining-{{ip}}', 3, 0.001); + $adapter->setParam('{{ip}}', '0.0.0.12'); + $abuse = new Abuse($adapter); + + $this->assertSame(2, $adapter->remaining()); // full bucket: limit - (0 + 1) + $this->assertSame(false, $abuse->check()); // 1 consumed + $this->assertSame(1, $adapter->remaining()); + $this->assertSame(false, $abuse->check()); // 2 consumed + $this->assertSame(0, $adapter->remaining()); + } + + /** + * Test that tokens refill over time + */ + public function testRefill(): void + { + // 1 token/sec, capacity 1: consume it, then a refill lets one more through + $adapter = $this->getAdapter('tb-refill-{{ip}}', 1, 1.0); + $adapter->setParam('{{ip}}', '0.0.0.13'); + $abuse = new Abuse($adapter); + + $this->assertSame(false, $abuse->check()); // consume the only token + $this->assertSame(true, $abuse->check()); // empty, throttled + + sleep(2); // refill ~2 tokens (capped at capacity 1) + + $this->assertSame(false, $abuse->check()); // refilled, allowed again + } + + /** + * Verify that time() returns the current time as an int + */ + public function testTimeFormat(): void + { + $adapter = $this->getAdapter('tb-time', 1, 1.0); + $this->assertSame(true, \is_int($adapter->time())); + } + + /** + * Test the reset functionality refills the bucket + */ + public function testReset(): void + { + $adapter = $this->getAdapter('tb-reset-test-{{ip}}', 5, 0.001); + $adapter->setParam('{{ip}}', '192.168.1.1'); + $abuse = new Abuse($adapter); + + // 5 OK, 6th limited + for ($i = 0; $i < 5; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + + // Reset refills the bucket + $abuse->reset(); + + // 5 more OK, then limited again + for ($i = 0; $i < 5; $i++) { + $this->assertSame(false, $abuse->check()); + } + $this->assertSame(true, $abuse->check()); + } + + /** + * Test that a non-positive refill rate is rejected + */ + public function testRefillRateGuard(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->getAdapter('tb-guard', 1, 0.0); + } + + /** + * Test that limit 0 means unlimited + */ + public function testUnlimited(): void + { + $adapter = $this->getAdapter('tb-unlimited', 0, 1.0); + $abuse = new Abuse($adapter); + for ($i = 0; $i < 20; $i++) { + $this->assertSame(false, $abuse->check()); + } + } +} diff --git a/tests/Abuse/TokenBucket/RedisClusterTest.php b/tests/Abuse/TokenBucket/RedisClusterTest.php new file mode 100644 index 00000000000..7b9a0c58227 --- /dev/null +++ b/tests/Abuse/TokenBucket/RedisClusterTest.php @@ -0,0 +1,45 @@ +close(); + } + } +} diff --git a/tests/Abuse/TokenBucket/RedisPoolTest.php b/tests/Abuse/TokenBucket/RedisPoolTest.php new file mode 100644 index 00000000000..750666f4be7 --- /dev/null +++ b/tests/Abuse/TokenBucket/RedisPoolTest.php @@ -0,0 +1,53 @@ +|null + */ + protected static ?Pool $pool = null; + + public static function setUpBeforeClass(): void + { + if (isset(self::$pool)) { + return; + } + + self::$pool = new Pool(new Stack(), 'abuse-tb-redis', 2, function (): \Redis { + $redis = new \Redis(); + $redis->connect('redis', 6379); + + return $redis; + }, timeout: 0.0); + } + + public function getAdapter(string $key, int $tokens, float $refillRate): TokenBucket + { + $pool = self::$pool; + $this->assertInstanceOf(Pool::class, $pool); + + /** @var Pool<\Redis> $pool */ + return new AdapterRedisPool('tb-pool-' . $key, $tokens, $refillRate, $pool); + } + + public static function tearDownAfterClass(): void + { + if (!isset(self::$pool)) { + return; + } + + self::$pool->use(function (mixed $redis): void { + if ($redis instanceof \Redis) { + $redis->close(); + } + }); + self::$pool = null; + } +} diff --git a/tests/Abuse/TokenBucket/RedisTest.php b/tests/Abuse/TokenBucket/RedisTest.php new file mode 100644 index 00000000000..bb1b94ade0e --- /dev/null +++ b/tests/Abuse/TokenBucket/RedisTest.php @@ -0,0 +1,43 @@ +connect('redis', 6379); + + return $redis; + } + + public function getAdapter(string $key, int $tokens, float $refillRate): TokenBucket + { + return new AdapterRedis($key, $tokens, $refillRate, self::$redis); + } + + public static function tearDownAfterClass(): void + { + if (isset(self::$redis)) { + self::$redis->close(); + } + } +} From 70e1e4cdb5b9bf953e15b3d052e7c0a7444b2703 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 11:48:15 +0530 Subject: [PATCH 370/470] fix(token-bucket): floor available balance before deriving consumed count Fractional refill made floor(capacity - balance) undercount consumed tokens by one (e.g. balance 1.00001 -> 1 consumed instead of 2). Floor the available balance first, then subtract from capacity so remaining() stays exact. --- src/Abuse/Adapters/TokenBucket/RedisBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Abuse/Adapters/TokenBucket/RedisBase.php b/src/Abuse/Adapters/TokenBucket/RedisBase.php index fb633b83164..f806b7bd5e8 100644 --- a/src/Abuse/Adapters/TokenBucket/RedisBase.php +++ b/src/Abuse/Adapters/TokenBucket/RedisBase.php @@ -165,7 +165,7 @@ public function check(): bool // consumed count so a following remaining() stays consistent with it. [$allowed, $available] = $result; $balance = \is_numeric($available) ? (float) $available : 0.0; - $this->count = (int) \floor($this->tokens - $balance); + $this->count = $this->tokens - (int) \floor($balance); return (int) $allowed === 0; } @@ -207,7 +207,7 @@ protected function count(string $key, int $timestamp): int ); $balance = \is_numeric($raw) ? (float) $raw : (float) $this->tokens; - $this->count = (int) \floor($this->tokens - $balance); + $this->count = $this->tokens - (int) \floor($balance); return $this->count; } From 6c152d3e3f4a98b80fed6e5e56459b351674e104 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 12:12:19 +0530 Subject: [PATCH 371/470] fix(token-bucket): always read a fresh refill estimate in count() The bucket refills continuously, so caching the consumed count made remaining() report a stale quota once time elapsed after a check(). Drop the cache and compute a fresh estimate on every count(); check() now only needs the allow decision. --- src/Abuse/Adapters/TokenBucket.php | 5 ----- src/Abuse/Adapters/TokenBucket/RedisBase.php | 19 ++++--------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Abuse/Adapters/TokenBucket.php b/src/Abuse/Adapters/TokenBucket.php index 899ce6e2c9e..19d50d30352 100644 --- a/src/Abuse/Adapters/TokenBucket.php +++ b/src/Abuse/Adapters/TokenBucket.php @@ -15,11 +15,6 @@ abstract class TokenBucket extends Adapter */ protected int $tokens = 0; - /** - * @var int|null - */ - protected ?int $count = null; - /** * @var int */ diff --git a/src/Abuse/Adapters/TokenBucket/RedisBase.php b/src/Abuse/Adapters/TokenBucket/RedisBase.php index f806b7bd5e8..6fa8ec9cdfa 100644 --- a/src/Abuse/Adapters/TokenBucket/RedisBase.php +++ b/src/Abuse/Adapters/TokenBucket/RedisBase.php @@ -161,11 +161,7 @@ public function check(): bool ], ); - // $available is the token balance left after consuming; store the - // consumed count so a following remaining() stays consistent with it. - [$allowed, $available] = $result; - $balance = \is_numeric($available) ? (float) $available : 0.0; - $this->count = $this->tokens - (int) \floor($balance); + [$allowed] = $result; return (int) $allowed === 0; } @@ -175,8 +171,8 @@ public function check(): bool * * Read-only estimate of the tokens already consumed from the bucket * (capacity minus the tokens available after refilling). Used by remaining(). - * Reuses the value recorded by the most recent check()/reset() this request; - * otherwise reads a fresh estimate from storage. + * The bucket refills continuously, so this always reads a fresh estimate + * rather than reusing a cached value that would go stale as tokens refill. * * @param string $key * @param int $timestamp @@ -190,10 +186,6 @@ protected function count(string $key, int $timestamp): int $this->timestamp = \time(); - if ($this->count !== null) { - return $this->count; - } - $raw = $this->eval( self::TOKENS_SCRIPT, [ @@ -207,9 +199,8 @@ protected function count(string $key, int $timestamp): int ); $balance = \is_numeric($raw) ? (float) $raw : (float) $this->tokens; - $this->count = $this->tokens - (int) \floor($balance); - return $this->count; + return $this->tokens - (int) \floor($balance); } /** @@ -222,8 +213,6 @@ protected function count(string $key, int $timestamp): int public function reset(): void { $this->delete($this->bucketKey($this->parseKey())); - - $this->count = 0; } /** From 6a011fe110fca922ac5cfdc7d91aff68994f6fda Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 12:12:19 +0530 Subject: [PATCH 372/470] fix(token-bucket): scan every cluster master fully before paginating logs Stopping each master's unordered scan at offset+limit and then sorting/slicing the partial candidate set returned a biased page and could omit keys in range. Scan all masters completely, then sort and slice. --- src/Abuse/Adapters/TokenBucket/RedisCluster.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Abuse/Adapters/TokenBucket/RedisCluster.php b/src/Abuse/Adapters/TokenBucket/RedisCluster.php index ced320c7ee4..beb86ec0a1c 100644 --- a/src/Abuse/Adapters/TokenBucket/RedisCluster.php +++ b/src/Abuse/Adapters/TokenBucket/RedisCluster.php @@ -61,7 +61,7 @@ public function getLogs(?int $offset = 0, ?int $limit = 25): array if ($keys !== false) { $matches = array_merge($matches, $keys); } - } while ($cursor > 0 && count($matches) < $offset + $limit); + } while ($cursor > 0); } sort($matches); From e5000d3a68460d836eea9b8534aa6469a7b066d4 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 15:35:47 +0530 Subject: [PATCH 373/470] feat(sliding-window): add None adapter --- src/Abuse/Adapters/SlidingWindow/None.php | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/Abuse/Adapters/SlidingWindow/None.php diff --git a/src/Abuse/Adapters/SlidingWindow/None.php b/src/Abuse/Adapters/SlidingWindow/None.php new file mode 100644 index 00000000000..a7e59b9b933 --- /dev/null +++ b/src/Abuse/Adapters/SlidingWindow/None.php @@ -0,0 +1,58 @@ +key = $key; + $this->limit = $limit; + $now = \time(); + $this->timestamp = (int) ($now - ($now % $windowSize)); + } + + protected function count(string $key, int $timestamp): int + { + return 0; + } + + public function check(): bool + { + return false; + } + + public function reset(): void + { + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + return []; + } + + /** + * Delete all logs older than $timestamp + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } +} From 4afb6a26477b64a61c32037ead4acdb45d2b8966 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 15:36:21 +0530 Subject: [PATCH 374/470] feat(token-bucket): add None adapter --- src/Abuse/Adapters/TokenBucket/None.php | 57 +++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/Abuse/Adapters/TokenBucket/None.php diff --git a/src/Abuse/Adapters/TokenBucket/None.php b/src/Abuse/Adapters/TokenBucket/None.php new file mode 100644 index 00000000000..cdec3544cc1 --- /dev/null +++ b/src/Abuse/Adapters/TokenBucket/None.php @@ -0,0 +1,57 @@ +key = $key; + $this->tokens = $tokens; + $this->timestamp = \time(); + } + + protected function count(string $key, int $timestamp): int + { + return 0; + } + + public function check(): bool + { + return false; + } + + public function reset(): void + { + } + + /** + * Get abuse logs + * + * Return logs with an offset and limit + * + * @param int|null $offset + * @param int|null $limit + * @return array + */ + public function getLogs(?int $offset = null, ?int $limit = 25): array + { + return []; + } + + /** + * Delete all logs older than $timestamp + * + * @param int $timestamp + * @return bool + */ + public function cleanup(int $timestamp): bool + { + return true; + } +} From fba428fc14618cf1502c26723a806a5200fb1c6f Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 15:47:02 +0530 Subject: [PATCH 375/470] fix(sliding-window): guard None against zero-sized window --- src/Abuse/Adapters/SlidingWindow/None.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Abuse/Adapters/SlidingWindow/None.php b/src/Abuse/Adapters/SlidingWindow/None.php index a7e59b9b933..74f9554d072 100644 --- a/src/Abuse/Adapters/SlidingWindow/None.php +++ b/src/Abuse/Adapters/SlidingWindow/None.php @@ -11,6 +11,10 @@ class None extends SlidingWindow */ public function __construct(string $key, int $limit, int $windowSize, int $ttl) // @phpstan-ignore constructor.unusedParameter { + if ($windowSize <= 0) { + throw new \InvalidArgumentException('windowSize must be greater than 0'); + } + $this->key = $key; $this->limit = $limit; $now = \time(); From 49ac83a8a7072f088d1ddb384a3beeceb4c45f2d Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Thu, 13 Aug 2026 15:47:02 +0530 Subject: [PATCH 376/470] fix(sliding-window): drop stale count cache so estimate keeps decaying --- src/Abuse/Adapters/SlidingWindow.php | 5 ---- .../Adapters/SlidingWindow/RedisBase.php | 25 ++----------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/Abuse/Adapters/SlidingWindow.php b/src/Abuse/Adapters/SlidingWindow.php index 6c6712ec19b..a488db643da 100644 --- a/src/Abuse/Adapters/SlidingWindow.php +++ b/src/Abuse/Adapters/SlidingWindow.php @@ -13,11 +13,6 @@ abstract class SlidingWindow extends Adapter */ protected int $limit = 0; - /** - * @var int|null - */ - protected ?int $count = null; - /** * @var int */ diff --git a/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/src/Abuse/Adapters/SlidingWindow/RedisBase.php index ade19adb4d8..39b357e0796 100644 --- a/src/Abuse/Adapters/SlidingWindow/RedisBase.php +++ b/src/Abuse/Adapters/SlidingWindow/RedisBase.php @@ -62,13 +62,6 @@ abstract class RedisBase extends SlidingWindow */ protected int $ttl; - /** - * Window start the cached $count was computed for (null when no cache). - * - * @var int|null - */ - protected ?int $countTimestamp = null; - /** * Run a Lua script against the storage backend. * @@ -195,11 +188,7 @@ public function check(): bool ], ); - // $estimate is the weighted sliding-window count, so a following - // remaining() call stays consistent with what check() decided on. - [$allowed, , $estimate] = $result; - $this->count = $estimate; - $this->countTimestamp = $timestamp; + [$allowed] = $result; return $allowed === 0; } @@ -223,20 +212,13 @@ protected function count(string $key, int $timestamp): int [$windowStart, $elapsed] = $this->window(); $this->timestamp = $windowStart; - if ($this->count !== null && $this->countTimestamp === $windowStart) { - return $this->count; - } - $currentRaw = $this->get($this->bucketKey($key, $windowStart)); $previousRaw = $this->get($this->bucketKey($key, $windowStart - $this->windowSize)); $current = \is_numeric($currentRaw) ? (int) $currentRaw : 0; $previous = \is_numeric($previousRaw) ? (int) $previousRaw : 0; - $this->count = (int) \floor($current + $previous * (1 - $elapsed)); - $this->countTimestamp = $windowStart; - - return $this->count; + return (int) \floor($current + $previous * (1 - $elapsed)); } /** @@ -256,9 +238,6 @@ public function reset(): void $this->bucketKey($key, $windowStart), $this->bucketKey($key, $windowStart - $this->windowSize), ); - - $this->count = 0; - $this->countTimestamp = $windowStart; } /** From 71e464ffb7209d80732d5a6a212b476944884e93 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:23:38 +0200 Subject: [PATCH 377/470] Merge pull request #164 from utopia-php/feat/queue-nats-backoff-storage-dlq-ttl feat(queue,nats): consumer backoff, stream storage choice, dead-letter TTL --- src/JetStream/ConsumerConfig.php | 9 +++++++++ tests/Unit/JetStream/ConfigTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/JetStream/ConsumerConfig.php b/src/JetStream/ConsumerConfig.php index bd97c10218a..d040646df2f 100644 --- a/src/JetStream/ConsumerConfig.php +++ b/src/JetStream/ConsumerConfig.php @@ -12,6 +12,10 @@ final class ConsumerConfig * @param float|null $idleHeartbeat Idle heartbeat interval in seconds (push consumers) * @param list|null $filterSubjects * @param array|null $metadata Arbitrary key-value metadata (ADR-33) + * @param list|null $backoff Redelivery delays in seconds, one per attempt + * (the last entry repeats). Server rules, not sanitized here: when set + * together with $ackWait the first entry must equal $ackWait, and + * $maxDeliver must exceed the number of entries. */ public function __construct( public readonly ?string $name = null, @@ -38,6 +42,7 @@ public function __construct( public readonly bool $flowControl = false, public readonly ?float $idleHeartbeat = null, public readonly ?array $metadata = null, + public readonly ?array $backoff = null, ) {} public function toArray(): array @@ -111,6 +116,9 @@ public function toArray(): array if ($this->metadata !== null) { $data['metadata'] = $this->metadata; } + if ($this->backoff !== null) { + $data['backoff'] = array_map(StreamConfig::secondsToNanos(...), $this->backoff); + } return $data; } @@ -142,6 +150,7 @@ public static function fromArray(array $data): self flowControl: $data['flow_control'] ?? false, idleHeartbeat: isset($data['idle_heartbeat']) ? StreamConfig::nanosToSeconds($data['idle_heartbeat']) : null, metadata: $data['metadata'] ?? null, + backoff: isset($data['backoff']) ? array_map(StreamConfig::nanosToSeconds(...), $data['backoff']) : null, ); } } diff --git a/tests/Unit/JetStream/ConfigTest.php b/tests/Unit/JetStream/ConfigTest.php index d0bec7d46df..3f4a76a474f 100644 --- a/tests/Unit/JetStream/ConfigTest.php +++ b/tests/Unit/JetStream/ConfigTest.php @@ -60,6 +60,33 @@ public function testConsumerInfoPopulatesSequences(): void $this->assertSame(7, $info->ackFloor->streamSeq); } + public function testBackoffSerializesToNanos(): void + { + $config = new ConsumerConfig( + ackWait: 10.0, + maxDeliver: 5, + backoff: [10.0, 30.0, 120.0], + ); + + $arr = $config->toArray(); + $this->assertSame([10_000_000_000, 30_000_000_000, 120_000_000_000], $arr['backoff']); + $this->assertSame(10_000_000_000, $arr['ack_wait']); + } + + public function testBackoffOmittedWhenUnset(): void + { + $this->assertArrayNotHasKey('backoff', (new ConsumerConfig())->toArray()); + } + + public function testBackoffRoundTrip(): void + { + $config = ConsumerConfig::fromArray([ + 'backoff' => [5_000_000_000, 60_000_000_000], + ]); + + $this->assertSame([5.0, 60.0], $config->backoff); + } + public function testStreamMessageDecodesBase64Payload(): void { $msg = StreamMessage::fromArray([ From 49b5210738c36a21ae61eadc405889284784dec7 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:35:26 +0200 Subject: [PATCH 378/470] Merge pull request #173 from utopia-php/fix/nats-connection-death-detection fix(nats,pools,queue): stop losing work, dedupe enqueues, end duplicate delivery (CLO-4771/4772/4773) --- src/Connection.php | 270 ++++++++- src/JetStream/Consumer.php | 46 +- src/JetStream/JetStream.php | 7 + src/KeyValue/KeyValue.php | 17 +- src/ObjectStore/ObjectStore.php | 17 +- src/Protocol/Parser.php | 63 +- src/Transport/TcpTransport.php | 61 +- src/Transport/TlsTransport.php | 59 +- src/Transport/WebSocketTransport.php | 41 +- tests/Unit/ConnectionDeathDetectionTest.php | 615 ++++++++++++++++++++ tests/Unit/ConsumerFetchLifecycleTest.php | 138 +++++ tests/Unit/ParserFrameIntegrityTest.php | 85 +++ tests/Unit/Support/FakeTransport.php | 15 +- tests/Unit/WriteFailureSemanticsTest.php | 92 +++ 14 files changed, 1448 insertions(+), 78 deletions(-) create mode 100644 tests/Unit/ConnectionDeathDetectionTest.php create mode 100644 tests/Unit/ConsumerFetchLifecycleTest.php create mode 100644 tests/Unit/ParserFrameIntegrityTest.php create mode 100644 tests/Unit/WriteFailureSemanticsTest.php diff --git a/src/Connection.php b/src/Connection.php index d8238eb304a..b6f722a70e2 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -37,6 +37,50 @@ final class Connection private const CLIENT_LANG = 'php'; private const CLIENT_VERSION = '0.1.0'; + // Bounds on the read tick() does to collect its own PONGs. Short enough + // that a sweep over an idle pool stays in the low milliseconds, and capped + // so a connection with live subscription traffic cannot hold maintenance. + private const float TICK_READ_TIMEOUT = 0.05; + private const int TICK_MAX_READS = 4; + + /** + * Server -ERR substrings after which NATS closes the connection. Lowercase; + * matched as substrings because the server wraps some of them in quotes. + * + * @var list + */ + private const array CLOSING_ERRORS = [ + 'stale connection', + 'slow consumer', + 'maximum payload', + 'invalid client protocol', + 'authorization violation', + 'authentication timeout', + 'authentication expired', + 'secure connection - tls required', + 'maximum connections exceeded', + 'maximum control line exceeded', + 'unknown protocol operation', + 'parser error', + ]; + + /** + * The subset of {@see self::CLOSING_ERRORS} a reconnect cannot fix, so the + * connection is marked dead rather than rebuilt in a loop. + * + * @var list + */ + private const array UNRECOVERABLE_ERRORS = [ + 'authorization violation', + 'authentication timeout', + 'authentication expired', + 'secure connection - tls required', + 'maximum connections exceeded', + 'maximum control line exceeded', + 'unknown protocol operation', + 'parser error', + ]; + private Transport $transport; private Parser $parser; private readonly Writer $writer; @@ -263,6 +307,47 @@ public function newInbox(): string return Inbox::create($this->options->inboxPrefix); } + /** + * Drive connection maintenance without consuming a message: send the + * keepalive PING when it is due, and recycle the connection when the server + * has stopped answering. A holder that keeps a connection open without + * reading from it -- a pooled publisher, say -- must call this on an + * interval shorter than the server's ping deadline, because nothing else + * in this class runs on its own. + * + * This reads the socket, so the caller must hold the connection + * exclusively for the call. Pool maintenance satisfies that by sweeping + * only resources that are idle; a coroutine must never tick a connection + * another coroutine is inside a call on. + */ + public function tick(): void + { + $this->checkPings(); + $this->collectPongs(); + } + + /** + * Consume the PONGs owed for the keepalive PINGs this connection has sent. + * + * checkPings() only writes: it is the read path that clears + * $outstandingPings, and a holder that never reads has none. Ticking such a + * connection would therefore march it to maxPingsOut and declare a + * perfectly healthy socket stale -- the keepalive would cause the outage it + * exists to prevent. Reading here closes that loop. + * + * Bounded, and only while a PONG is actually owed, so maintenance can never + * turn into a receive loop on a connection carrying subscription traffic. + */ + private function collectPongs(): void + { + // readMessage() rather than processMessage(): the latter checks the + // keepalive on entry, so draining through it would send a fresh PING + // for every PONG collected and the outstanding count could never fall. + for ($read = 0; $this->outstandingPings > 0 && $read < self::TICK_MAX_READS; $read++) { + $this->readMessage(self::TICK_READ_TIMEOUT); + } + } + /** * Read and dispatch one server message. */ @@ -270,6 +355,15 @@ public function processMessage(?float $timeout = null): ?Message { $this->checkPings(); + return $this->readMessage($timeout); + } + + /** + * The read half of {@see self::processMessage()}, without the keepalive + * check, so maintenance can drain the socket without writing to it. + */ + private function readMessage(?float $timeout = null): ?Message + { try { [$op, $data] = $this->parser->next($timeout); } catch (TimeoutException) { @@ -634,12 +728,102 @@ private function handlePong(): null private function handleError(mixed $data): never { $message = \is_string($data) ? $data : 'Unknown server error'; + $error = self::mapServerError($message); if ($this->options->onError instanceof \Closure) { ($this->options->onError)(new NatsException($message)); } - throw self::mapServerError($message); + // The server has already closed the socket for these errors, but the + // status still reads "connected", so the next publish() would write into + // a dead socket and return success. Recycle the connection here instead. + if (self::closesConnection($message)) { + $this->recycleDeadConnection(self::reconnectsAfter($message)); + } + + throw $error; + } + + /** + * Recycle a connection the server has closed under us: reconnect when that + * can help, otherwise mark it dead so ensureConnected() refuses the next + * call. A reconnect that exhausts its attempts leaves the status at + * disconnected, and the -ERR that got us here is the more useful thing to + * report, so its ConnectionException is deliberately swallowed. + * + * Marking it dead is the important half. An error we decline to reconnect + * after is still an error the server closed the socket for, and leaving the + * status at connected would put us back in the state this whole path + * exists to remove: a publish that writes into a dead socket and returns + * success. + */ + private function recycleDeadConnection(bool $mayReconnect = true): void + { + if ($mayReconnect && $this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { + try { + $this->attemptReconnect(); + } catch (ConnectionException) { + // Status is disconnected; handleError() still throws the -ERR. + } + + return; + } + + $this->status = self::STATUS_DISCONNECTED; + + if (isset($this->transport)) { + $this->transport->close(); + } + } + + /** + * Whether NATS closes the connection after this -ERR. Purely a statement + * about the socket, deliberately separate from whether reconnecting is + * worth doing -- see {@see self::reconnectsAfter()}. + * + * The two were one predicate at first, which quietly recreated the bug this + * class is fixing: an error excluded from reconnect was also excluded from + * being recorded as closed, so an authorization failure left the status at + * connected over a socket the server had already dropped, and the next + * publish() wrote into it and returned success. + * + * Pure so the classification can be unit tested without a live connection. + */ + public static function closesConnection(string $message): bool + { + $lower = strtolower($message); + + foreach (self::CLOSING_ERRORS as $needle) { + if (str_contains($lower, $needle)) { + return true; + } + } + + return false; + } + + /** + * Whether rebuilding the connection can plausibly succeed after this -ERR. + * + * Only asked of errors that closed the connection. False for the ones a + * retry cannot fix -- credentials the server just rejected, a TLS + * requirement we are not meeting, a server at its connection ceiling, or + * protocol this client got wrong -- because reconnecting there turns a hard + * failure into a hot loop against a server that will refuse us identically + * every time. Those connections are marked dead instead, which surfaces the + * failure to the caller rather than hiding it behind a retry storm. + */ + public static function reconnectsAfter(string $message): bool + { + $lower = strtolower($message); + + foreach (self::UNRECOVERABLE_ERRORS as $needle) { + if (str_contains($lower, $needle)) { + return false; + } + } + + return true; } /** @@ -704,6 +888,16 @@ private function handleLameDuck(): void } } + /** + * Emit the keepalive PING when one is due, and act on a spent budget first. + * + * Only called from paths that also read the socket -- {@see + * self::processMessage()} and {@see self::tick()} -- because a PING is only + * half a keepalive. It is the read that clears $outstandingPings, so a + * caller that sends without reading marches the count to maxPingsOut and + * declares a healthy connection stale. See {@see self::ensureConnected()} + * for the write path, which takes the verdict without the emission. + */ private function checkPings(): void { if ($this->status !== self::STATUS_CONNECTED) { @@ -711,28 +905,52 @@ private function checkPings(): void } $now = microtime(true); - if (($now - $this->lastPingTime) >= $this->options->pingInterval) { - if ($this->outstandingPings >= $this->options->maxPingsOut) { - // Stale connection - if ($this->options->allowReconnect) { - $this->attemptReconnect(); - return; - } - throw new ConnectionException('Stale connection: too many outstanding pings'); - } + if (($now - $this->lastPingTime) < $this->options->pingInterval) { + return; + } - try { - $this->send($this->writer->ping()); - $this->outstandingPings++; - $this->lastPingTime = $now; - } catch (ConnectionException) { - if ($this->options->allowReconnect) { - $this->attemptReconnect(); - } + if ($this->checkStale()) { + return; + } + + try { + $this->send($this->writer->ping()); + $this->outstandingPings++; + $this->lastPingTime = $now; + } catch (ConnectionException) { + if ($this->options->allowReconnect) { + $this->attemptReconnect(); } } } + /** + * The staleness verdict on its own: the server has left this many PINGs + * unanswered, so the connection is gone. Reconnects where that is allowed + * and raises otherwise, and reports whether it acted so callers can stop. + * + * Split out of checkPings() so the write path can reach the verdict without + * the emission that goes with it. + */ + private function checkStale(): bool + { + if ($this->status !== self::STATUS_CONNECTED) { + return false; + } + + if ($this->outstandingPings < $this->options->maxPingsOut) { + return false; + } + + if ($this->options->allowReconnect) { + $this->attemptReconnect(); + + return true; + } + + throw new ConnectionException('Stale connection: too many outstanding pings'); + } + private function attemptReconnect(): void { if ($this->status === self::STATUS_CLOSED || $this->status === self::STATUS_RECONNECTING) { @@ -802,6 +1020,22 @@ private function ensureConnected(): void if ($this->status !== self::STATUS_CONNECTED && $this->status !== self::STATUS_DRAINING) { throw new ConnectionException("Not connected (status: {$this->status})"); } + + // Take the keepalive verdict before the caller writes rather than + // after: a connection that has already outlived the server's ping + // deadline is recycled here, so the write that follows lands on a live + // socket or raises. + // + // The verdict only, never the PING. checkPings() increments + // $outstandingPings and it is the read path that clears it, so emitting + // here would make every write-only caller -- a pooled publisher between + // maintenance sweeps, most of all -- accumulate one unanswered PING per + // interval and reconnect a perfectly healthy socket on a timer. A + // connection that is actively publishing is not idle, either: the + // server sees its traffic, so there is nothing for a PING from here to + // keep alive. Emission belongs to the paths that also read -- see + // {@see self::checkPings()}. + $this->checkStale(); } private function send(string $data): void diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index 63d28880bf2..b6dfca18fe9 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -6,9 +6,17 @@ use Utopia\NATS\Connection; use Utopia\NATS\Exception\TimeoutException; +use Utopia\NATS\Subscription; final class Consumer { + /** + * Seconds by which the server's pull-request expiry is pulled in ahead of + * the client deadline. Capped at a tenth of the timeout so a short fetch + * still leaves the server most of the window. + */ + private const FETCH_EXPIRY_MARGIN = 0.1; + public function __construct( private readonly Connection $conn, private readonly string $stream, @@ -24,9 +32,15 @@ public function fetch(int $batch = 1, ?float $timeout = null, bool $noWait = fal $timeout ??= 5.0; $requestSubject = "{$this->apiPrefix}.CONSUMER.MSG.NEXT.{$this->stream}.{$this->getName()}"; + // Expire the server's pull request just before the client stops waiting. + // On an identical deadline a message the server dispatches at the + // boundary is dropped here while the server has already counted the + // delivery, burning an attempt against maxDeliver for nothing. + $serverExpiry = $timeout - min(self::FETCH_EXPIRY_MARGIN, $timeout * 0.1); + $request = [ 'batch' => $batch, - 'expires' => StreamConfig::secondsToNanos($timeout), + 'expires' => StreamConfig::secondsToNanos($serverExpiry), ]; if ($noWait) { $request['no_wait'] = true; @@ -40,9 +54,31 @@ public function fetch(int $batch = 1, ?float $timeout = null, bool $noWait = fal $inbox = $this->conn->newInbox(); $sub = $this->conn->subscribe($inbox); - $this->conn->publish($requestSubject, $payload, $inbox); - $messageBatch = new MessageBatch($this->conn); + + try { + $this->conn->publish($requestSubject, $payload, $inbox); + $this->collectBatch($sub, $messageBatch, $batch, $timeout); + } finally { + // Outside a finally this leaked the inbox subscription on any throw, + // and attemptReconnect() re-subscribes every leaked sid on each + // reconnect -- so the leak compounds across a reconnect storm. + $sub->unsubscribe(); + } + + return $messageBatch; + } + + /** + * Gather up to $batch messages for a pull request, stopping on the server's + * terminal status messages or when the client deadline passes. + */ + private function collectBatch( + Subscription $sub, + MessageBatch $messageBatch, + int $batch, + float $timeout, + ): void { $deadline = microtime(true) + $timeout; while (\count($messageBatch) < $batch) { @@ -73,10 +109,6 @@ public function fetch(int $batch = 1, ?float $timeout = null, bool $noWait = fal $messageBatch->addMessage($msg); } - - $sub->unsubscribe(); - - return $messageBatch; } /** diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 4e827f5d765..3ac1015229e 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -12,6 +12,13 @@ final class JetStream { + /** + * JetStream API error code for a rejected optimistic-concurrency publish + * ("wrong last sequence"). This is the server evaluating the request and + * refusing it, which is the only failure a caller may read as a conflict. + */ + public const ERR_WRONG_LAST_SEQUENCE = 10071; + private readonly string $apiPrefix; public function __construct( diff --git a/src/KeyValue/KeyValue.php b/src/KeyValue/KeyValue.php index 51b86d4785c..68c738e2db0 100644 --- a/src/KeyValue/KeyValue.php +++ b/src/KeyValue/KeyValue.php @@ -5,6 +5,7 @@ namespace Utopia\NATS\KeyValue; use Utopia\NATS\Connection; +use Utopia\NATS\Exception\JetStreamException; use Utopia\NATS\Exception\KeyValueException; use Utopia\NATS\Headers; use Utopia\NATS\JetStream\AckPolicy; @@ -92,7 +93,15 @@ public function create(string $key, string $value): int try { $ack = $this->js->publish($subject, $value, $headers); - } catch (\Throwable $e) { + } catch (JetStreamException $e) { + // Only a rejected CAS publish means the key exists. Anything else -- + // a stale connection, a timeout, a permissions error -- must keep its + // own identity, because "you lost the race" is a verdict the caller + // acts on by not retrying. + if ($e->apiError?->errCode !== JetStream::ERR_WRONG_LAST_SEQUENCE) { + throw $e; + } + throw new KeyValueException("Key already exists: {$key}", $e->getCode(), previous: $e); } @@ -114,7 +123,11 @@ public function update(string $key, string $value, int $revision): int $value, expectedLastSubjectSeq: $revision, ); - } catch (\Throwable $e) { + } catch (JetStreamException $e) { + if ($e->apiError?->errCode !== JetStream::ERR_WRONG_LAST_SEQUENCE) { + throw $e; + } + throw new KeyValueException("Wrong last revision for key: {$key}", $e->getCode(), previous: $e); } diff --git a/src/ObjectStore/ObjectStore.php b/src/ObjectStore/ObjectStore.php index c3e338bc57a..f1ea229f260 100644 --- a/src/ObjectStore/ObjectStore.php +++ b/src/ObjectStore/ObjectStore.php @@ -5,6 +5,7 @@ namespace Utopia\NATS\ObjectStore; use Utopia\NATS\Connection; +use Utopia\NATS\Exception\JetStreamException; use Utopia\NATS\Exception\ObjectStoreException; use Utopia\NATS\Headers; use Utopia\NATS\JetStream\AckPolicy; @@ -87,7 +88,15 @@ private function writeVersion(string $name, string $data, ?ObjectMeta $previous, $headers, expectedLastSubjectSeq: $expectedSeq, ); - } catch (\Throwable $e) { + } catch (JetStreamException $e) { + // Purge only when the server actually rejected the CAS publish. On a + // transport failure the meta publish is ambiguous -- it may well have + // landed -- and purging here would delete the chunks this put just + // wrote successfully. + if ($e->apiError?->errCode !== JetStream::ERR_WRONG_LAST_SEQUENCE) { + throw $e; + } + $this->purgeChunks($nuid); throw new ObjectStoreException("conflicting concurrent write for object: {$name}", $e->getCode(), previous: $e); } @@ -425,7 +434,11 @@ private function publishMeta(ObjectMeta $meta, int $expectedSeq): void $headers, expectedLastSubjectSeq: $expectedSeq, ); - } catch (\Throwable $e) { + } catch (JetStreamException $e) { + if ($e->apiError?->errCode !== JetStream::ERR_WRONG_LAST_SEQUENCE) { + throw $e; + } + throw new ObjectStoreException("conflicting concurrent write for object: {$meta->name}", $e->getCode(), previous: $e); } } diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index 7f5db498508..e2b73b743b2 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -4,12 +4,14 @@ namespace Utopia\NATS\Protocol; +use Utopia\NATS\Exception\ConnectionException; use Utopia\NATS\Exception\ProtocolException; use Utopia\NATS\Transport\Transport; final class Parser { private string $buffer = ''; + private bool $poisoned = false; public function __construct( private readonly Transport $transport, @@ -22,6 +24,12 @@ public function __construct( */ public function next(?float $timeout = null): array { + // Once a frame body has been partially consumed the buffer no longer + // starts on a frame boundary, so nothing after that point can be parsed. + if ($this->poisoned) { + throw new ConnectionException('Parser desynced from the stream; the connection must be rebuilt'); + } + $line = $this->readLine($timeout); $line = rtrim($line, "\r\n"); @@ -59,12 +67,12 @@ public function next(?float $timeout = null): array // MSG [reply-to] <#bytes> if (str_starts_with($line, 'MSG ')) { - return $this->parseMsg(substr($line, 4)); + return $this->readFrame(fn(): array => $this->parseMsg(substr($line, 4), $timeout)); } // HMSG [reply-to] <#header-bytes> <#total-bytes> if (str_starts_with($line, 'HMSG ')) { - return $this->parseHmsg(substr($line, 5)); + return $this->readFrame(fn(): array => $this->parseHmsg(substr($line, 5), $timeout)); } throw new ProtocolException("Unknown protocol operation: {$line}"); @@ -75,7 +83,7 @@ public function next(?float $timeout = null): array * * @return array{0: ServerOp, 1: array{subject: string, sid: string, replyTo: ?string, payload: string}} */ - private function parseMsg(string $args): array + private function parseMsg(string $args, ?float $timeout = null): array { $parts = preg_split('/\s+/', trim($args)); @@ -91,9 +99,9 @@ private function parseMsg(string $args): array } $bytes = (int) $byteCount; - $payload = $this->readExactly($bytes); + $payload = $this->readExactly($bytes, $timeout); // Consume trailing \r\n - $this->readExactly(2); + $this->readExactly(2, $timeout); return [ServerOp::Msg, [ 'subject' => $subject, @@ -109,7 +117,7 @@ private function parseMsg(string $args): array * * @return array{0: ServerOp, 1: array{subject: string, sid: string, replyTo: ?string, payload: string, headers: string}} */ - private function parseHmsg(string $args): array + private function parseHmsg(string $args, ?float $timeout = null): array { $parts = preg_split('/\s+/', trim($args)); @@ -132,10 +140,10 @@ private function parseHmsg(string $args): array throw new ProtocolException("Invalid HMSG byte counts: header={$hdrLen}, total={$totalLen}"); } - $headerBlock = $this->readExactly($hdrLen); - $payload = $payloadLen > 0 ? $this->readExactly($payloadLen) : ''; + $headerBlock = $this->readExactly($hdrLen, $timeout); + $payload = $payloadLen > 0 ? $this->readExactly($payloadLen, $timeout) : ''; // Consume trailing \r\n - $this->readExactly(2); + $this->readExactly(2, $timeout); return [ServerOp::HMsg, [ 'subject' => $subject, @@ -170,10 +178,18 @@ private function readLine(?float $timeout = null): string } } - private function readExactly(int $bytes): string + /** + * Read exactly $bytes, passing the timeout explicitly on every transport + * read. Previously it passed none, so the read inherited whatever deadline + * the last readLine() happened to leave on the stream. The timeout applies + * per read rather than to the frame as a whole: a large payload arrives + * across several segments, and charging the whole body to one deadline + * would fail frames that are making steady progress. + */ + private function readExactly(int $bytes, ?float $timeout = null): string { while (\strlen($this->buffer) < $bytes) { - $data = $this->transport->read(max(65536, $bytes - \strlen($this->buffer))); + $data = $this->transport->read(max(65536, $bytes - \strlen($this->buffer)), $timeout); if ($data === '') { throw new ProtocolException('Unexpected end of data while reading payload'); } @@ -184,4 +200,29 @@ private function readExactly(int $bytes): string $this->buffer = substr($this->buffer, $bytes); return $result; } + + /** + * Run a frame-body read, turning any failure into a poisoned parser. + * + * The header line is already consumed by the time this runs, so a partial + * body read leaves the buffer mid-frame. Reporting that as a timeout would + * be indistinguishable from "nothing arrived" -- the caller would carry on + * against a stream whose next bytes are payload, not protocol. Raising + * ConnectionException instead routes it to the reconnect path, which builds + * a fresh parser. The dropped frame is often the PubAck itself. + * + * @param \Closure(): array{0: ServerOp, 1: mixed} $read + * @return array{0: ServerOp, 1: mixed} + */ + private function readFrame(\Closure $read): array + { + try { + return $read(); + } catch (\Throwable $e) { + $this->poisoned = true; + $this->buffer = ''; + + throw new ConnectionException("Failed mid-frame, parser desynced from the stream: {$e->getMessage()}", $e->getCode(), previous: $e); + } + } } diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index 0b5657cd534..0d824260a71 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -37,19 +37,26 @@ public function connect(string $host, int $port, float $timeout): void public function write(string $data): int { - $stream = $this->ensureConnected(); + $this->ensureConnected(); $total = \strlen($data); $written = 0; // Loop until every byte is on the wire: fwrite may perform a short write. + // Re-resolved on every pass rather than captured once. fwrite() is a + // yield point under Swoole's stream hooks, so a close() can land between + // two iterations of a short write -- and the next fwrite() on the + // handle the first pass captured raises TypeError, an \Error, which is + // precisely what the reconnect paths in Connection do not catch. The + // classification helpers below were hardened for this; the write itself + // was still holding the stale handle. while ($written < $total) { - $chunk = @fwrite($stream, substr($data, $written)); + $chunk = @fwrite($this->ensureConnected(), substr($data, $written)); if ($chunk === false || $chunk === 0) { - if ($this->isTimedOut($stream)) { + if ($this->isTimedOut()) { throw new TimeoutException('Write timed out'); } - if (feof($stream)) { + if ($this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } throw new ConnectionException('Failed to write to socket'); @@ -72,13 +79,13 @@ public function read(int $maxBytes, ?float $timeout = null): string $data = @fread($stream, $maxBytes); if ($data === false) { - if ($this->isTimedOut($stream)) { + if ($this->isTimedOut()) { throw new TimeoutException('Read timed out'); } throw new ConnectionException('Failed to read from socket'); } - if ($data === '' && feof($stream)) { + if ($data === '' && $this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } @@ -96,10 +103,10 @@ public function readLine(?float $timeout = null): string $line = @fgets($stream); if ($line === false) { - if ($this->isTimedOut($stream)) { + if ($this->isTimedOut()) { throw new TimeoutException('Read timed out'); } - if (feof($stream)) { + if ($this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } throw new ConnectionException('Failed to read line from socket'); @@ -146,7 +153,7 @@ public function upgradeTls(array $options): void public function isConnected(): bool { - return $this->stream !== null && !feof($this->stream); + return \is_resource($this->stream) && !feof($this->stream); } public function close(): void @@ -160,7 +167,12 @@ public function close(): void /** @return resource */ private function ensureConnected() { - if ($this->stream === null) { + // A null stream means close() ran; a non-resource stream means it ran + // concurrently and left a closed handle behind. Both are "not + // connected", and the second must be caught here because every stream + // function raises TypeError on a closed resource -- an \Error, which + // the reconnect paths in Connection do not catch. + if (!\is_resource($this->stream)) { throw new ConnectionException('Not connected'); } @@ -175,10 +187,31 @@ private function setTimeout($stream, float $timeout): void stream_set_timeout($stream, $seconds, $microseconds); } - /** @param resource $stream */ - private function isTimedOut($stream): bool + /** + * Whether the stream hit its timeout. Re-reads the property instead of + * trusting the caller's copy: a concurrent close() invalidates the resource + * the caller captured, and stream_get_meta_data() would raise TypeError on + * it. A stream that is gone did not time out -- it is EOF. + */ + private function isTimedOut(): bool { - $info = stream_get_meta_data($stream); - return $info['timed_out']; + if (!\is_resource($this->stream)) { + return false; + } + + return stream_get_meta_data($this->stream)['timed_out']; + } + + /** + * Whether the stream is at end of file. A closed or vanished stream counts + * as EOF so callers report "connection closed" rather than raising. + */ + private function isAtEof(): bool + { + if (!\is_resource($this->stream)) { + return true; + } + + return feof($this->stream); } } diff --git a/src/Transport/TlsTransport.php b/src/Transport/TlsTransport.php index 1fbd3e87d92..b1735bf5653 100644 --- a/src/Transport/TlsTransport.php +++ b/src/Transport/TlsTransport.php @@ -47,20 +47,26 @@ public function connect(string $host, int $port, float $timeout): void public function write(string $data): int { - $stream = $this->ensureConnected(); + $this->ensureConnected(); $total = \strlen($data); $written = 0; // Loop until every byte is on the wire: fwrite may perform a short write. + // Re-resolved on every pass rather than captured once. fwrite() is a + // yield point under Swoole's stream hooks, so a close() can land between + // two iterations of a short write -- and the next fwrite() on the + // handle the first pass captured raises TypeError, an \Error, which is + // precisely what the reconnect paths in Connection do not catch. The + // classification helpers below were hardened for this; the write itself + // was still holding the stale handle. while ($written < $total) { - $chunk = @fwrite($stream, substr($data, $written)); + $chunk = @fwrite($this->ensureConnected(), substr($data, $written)); if ($chunk === false || $chunk === 0) { - $info = stream_get_meta_data($stream); - if ($info['timed_out']) { + if ($this->isTimedOut()) { throw new TimeoutException('Write timed out'); } - if (feof($stream)) { + if ($this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } throw new ConnectionException('Failed to write to TLS socket'); @@ -85,14 +91,13 @@ public function read(int $maxBytes, ?float $timeout = null): string $data = @fread($stream, $maxBytes); if ($data === false) { - $info = stream_get_meta_data($stream); - if ($info['timed_out']) { + if ($this->isTimedOut()) { throw new TimeoutException('Read timed out'); } throw new ConnectionException('Failed to read from TLS socket'); } - if ($data === '' && feof($stream)) { + if ($data === '' && $this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } @@ -112,11 +117,10 @@ public function readLine(?float $timeout = null): string $line = @fgets($stream); if ($line === false) { - $info = stream_get_meta_data($stream); - if ($info['timed_out']) { + if ($this->isTimedOut()) { throw new TimeoutException('Read timed out'); } - if (feof($stream)) { + if ($this->isAtEof()) { throw new ConnectionException('Connection closed by server'); } throw new ConnectionException('Failed to read line from TLS socket'); @@ -132,7 +136,7 @@ public function upgradeTls(array $options): void public function isConnected(): bool { - return $this->stream !== null && !feof($this->stream); + return \is_resource($this->stream) && !feof($this->stream); } public function close(): void @@ -169,10 +173,39 @@ private function buildSslOptions(): array /** @return resource */ private function ensureConnected() { - if ($this->stream === null) { + // See TcpTransport::ensureConnected(): a concurrently closed stream is + // still a resource-typed property but raises TypeError on use, and that + // \Error bypasses the reconnect paths in Connection. + if (!\is_resource($this->stream)) { throw new ConnectionException('Not connected'); } return $this->stream; } + + /** + * Whether the stream hit its timeout, re-reading the property rather than + * trusting a caller's copy that a concurrent close() may have invalidated. + */ + private function isTimedOut(): bool + { + if (!\is_resource($this->stream)) { + return false; + } + + return stream_get_meta_data($this->stream)['timed_out']; + } + + /** + * Whether the stream is at end of file. A closed or vanished stream counts + * as EOF so callers report "connection closed" rather than raising. + */ + private function isAtEof(): bool + { + if (!\is_resource($this->stream)) { + return true; + } + + return feof($this->stream); + } } diff --git a/src/Transport/WebSocketTransport.php b/src/Transport/WebSocketTransport.php index 479f0049f25..00176069bbd 100644 --- a/src/Transport/WebSocketTransport.php +++ b/src/Transport/WebSocketTransport.php @@ -107,7 +107,7 @@ public function upgradeTls(array $options): void public function isConnected(): bool { - return $this->stream !== null && !feof($this->stream); + return \is_resource($this->stream) && !feof($this->stream); } public function close(): void @@ -227,12 +227,19 @@ private function writeFrame(string $payload, int $opcode = 0x2): void private function rawWrite(string $data): void { - $stream = $this->ensureConnected(); + $this->ensureConnected(); $total = \strlen($data); $written = 0; + // Re-resolved on every pass rather than captured once. fwrite() is a + // yield point under Swoole's stream hooks, so a close() can land between + // two iterations of a short write -- and the next fwrite() on the + // handle the first pass captured raises TypeError, an \Error, which is + // precisely what the reconnect paths in Connection do not catch. The + // classification helpers below were hardened for this; the write itself + // was still holding the stale handle. while ($written < $total) { - $chunk = @fwrite($stream, substr($data, $written)); + $chunk = @fwrite($this->ensureConnected(), substr($data, $written)); if ($chunk === false || $chunk === 0) { throw new ConnectionException('Failed to write to WebSocket'); } @@ -242,14 +249,16 @@ private function rawWrite(string $data): void private function rawRead(int $length): string { - $stream = $this->ensureConnected(); + $this->ensureConnected(); $data = ''; + // Re-resolved per pass for the same reason as rawWrite(): fread() yields, + // so a partial read spans a window in which close() can invalidate the + // handle this loop would otherwise keep using. while (\strlen($data) < $length) { - $chunk = @fread($stream, $length - \strlen($data)); + $chunk = @fread($this->ensureConnected(), $length - \strlen($data)); if ($chunk === false || $chunk === '') { - $info = stream_get_meta_data($stream); - if ($info['timed_out']) { + if ($this->isTimedOut()) { throw new TimeoutException('Read timed out'); } throw new ConnectionException('Connection closed by server'); @@ -263,13 +272,29 @@ private function rawRead(int $length): string /** @return resource */ private function ensureConnected() { - if ($this->stream === null) { + // See TcpTransport::ensureConnected(): a concurrently closed stream is + // still a resource-typed property but raises TypeError on use, and that + // \Error bypasses the reconnect paths in Connection. + if (!\is_resource($this->stream)) { throw new ConnectionException('Not connected'); } return $this->stream; } + /** + * Whether the stream hit its timeout, re-reading the property rather than + * trusting a caller's copy that a concurrent close() may have invalidated. + */ + private function isTimedOut(): bool + { + if (!\is_resource($this->stream)) { + return false; + } + + return stream_get_meta_data($this->stream)['timed_out']; + } + /** @param resource $stream */ private function setTimeout($stream, float $timeout): void { diff --git a/tests/Unit/ConnectionDeathDetectionTest.php b/tests/Unit/ConnectionDeathDetectionTest.php new file mode 100644 index 00000000000..fbf89854e02 --- /dev/null +++ b/tests/Unit/ConnectionDeathDetectionTest.php @@ -0,0 +1,615 @@ + $extra */ + private function connect(FakeTransport $fake, array $extra = []): Connection + { + return Connection::connect(new ConnectionOptions(...array_merge([ + 'servers' => 'nats://127.0.0.1:4222', + 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + // Keep reconnects instant and free of jitter so the assertions below + // are about behaviour rather than timing. + 'reconnectWait' => 0.0, + 'reconnectJitter' => 0.0, + ], $extra))); + } + + // --- -ERR classification (pure) --- + /** + * @return \Iterator, array{string, bool}> + */ + public static function serverErrorProvider(): \Iterator + { + yield ['Stale Connection', true]; + yield ['stale connection', true]; + yield ['Slow Consumer', true]; + yield ['Maximum Payload Exceeded', true]; + yield ['Invalid Client Protocol', true]; + // Closing too, even though reconnecting after them is refused: whether + // the socket is gone and whether a retry can help are separate + // questions. See reconnectsAfterProvider(). + yield ['Authorization Violation', true]; + yield ['Authentication Expired', true]; + yield ['Secure Connection - TLS Required', true]; + yield ['Maximum Connections Exceeded', true]; + yield ['Unknown Protocol Operation', true]; + // Genuinely not connection-closing. + yield ["Permissions Violation for Publish to 'foo'", false]; + yield ['Maximum Subscriptions Exceeded', false]; + yield ['Invalid Subject', false]; + yield ['', false]; + } + + #[DataProvider('serverErrorProvider')] + public function testClosesConnectionClassifiesServerErrors(string $message, bool $expected): void + { + $this->assertSame($expected, Connection::closesConnection($message)); + } + + /** + * @return \Iterator, array{string, bool}> + */ + public static function reconnectsAfterProvider(): \Iterator + { + // Transient or infrastructural: a fresh connection is a real fix. + yield ['Stale Connection', true]; + yield ['Slow Consumer', true]; + yield ['Maximum Payload Exceeded', true]; + // Nothing a retry can change -- reconnecting would hot-loop against a + // server that refuses us identically every time. + yield ['Authorization Violation', false]; + yield ['Authentication Timeout', false]; + yield ['Authentication Expired', false]; + yield ['Secure Connection - TLS Required', false]; + yield ['Maximum Connections Exceeded', false]; + yield ['Unknown Protocol Operation', false]; + } + + #[DataProvider('reconnectsAfterProvider')] + public function testReconnectsAfterSeparatesRecoverableErrors(string $message, bool $expected): void + { + $this->assertSame($expected, Connection::reconnectsAfter($message)); + } + + // --- Finding: reconnect on a connection-closing -ERR --- + + public function testConnectionClosingErrorTriggersReconnect(): void + { + $reconnected = false; + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'onReconnect' => function () use (&$reconnected): void { + $reconnected = true; + }, + ]); + + $fake->pushInbound("-ERR 'Stale Connection'\r\n"); + + // The error still surfaces to the caller, but the connection underneath + // has been rebuilt rather than left dead-but-"connected". + try { + $conn->processMessage(1.0); + $this->fail('Expected the -ERR to surface'); + } catch (ProtocolException) { + // expected + } + + $this->assertTrue($reconnected, 'A connection-closing -ERR must reconnect'); + $this->assertTrue($conn->isConnected()); + + $conn->close(); + } + + public function testConnectionClosingErrorWithoutReconnectMarksConnectionDead(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, ['allowReconnect' => false]); + + $fake->pushInbound("-ERR 'Stale Connection'\r\n"); + + try { + $conn->processMessage(1.0); + $this->fail('Expected the -ERR to surface'); + } catch (ProtocolException) { + // expected + } + + // The whole point: status no longer claims a usable connection, so the + // next publish raises instead of writing into a dead socket. + $this->assertFalse($conn->isConnected()); + $this->expectException(ConnectionException::class); + $conn->publish('foo', 'bar'); + } + + /** + * The regression the split predicates exist for. + * + * An authorization failure closes the connection server-side but must not + * be reconnected. When one predicate answered both questions, "do not + * reconnect" also meant "do not record it as closed", so the status stayed + * connected over a socket the server had already dropped -- and the next + * publish() wrote into it and returned success. Exactly the silent loss the + * -ERR handling was added to remove, on a different error. + */ + public function testUnrecoverableClosingErrorMarksConnectionDeadWithoutReconnecting(): void + { + $reconnected = false; + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'onReconnect' => function () use (&$reconnected): void { + $reconnected = true; + }, + ]); + + $fake->pushInbound("-ERR 'Authorization Violation'\r\n"); + + try { + $conn->processMessage(1.0); + $this->fail('Expected the -ERR to surface'); + } catch (AuthenticationException) { + // expected + } + + $this->assertFalse($reconnected, 'Credentials the server just rejected must not be retried'); + $this->assertFalse($conn->isConnected(), 'The socket is gone; the status must say so'); + + // The half that matters: the next write raises instead of vanishing. + $this->expectException(ConnectionException::class); + $conn->publish('foo', 'bar'); + } + + public function testNonClosingErrorLeavesConnectionIntact(): void + { + $reconnected = false; + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'onReconnect' => function () use (&$reconnected): void { + $reconnected = true; + }, + ]); + + $fake->pushInbound("-ERR 'Permissions Violation for Publish to \"foo\"'\r\n"); + + try { + $conn->processMessage(1.0); + $this->fail('Expected the -ERR to surface'); + } catch (PermissionException) { + // expected + } + + $this->assertFalse($reconnected, 'A permissions error must not recycle the connection'); + $this->assertTrue($conn->isConnected()); + + $conn->close(); + } + + // --- Finding: keepalive independent of the caller --- + + public function testTickSendsKeepalivePingWhenDue(): void + { + $fake = new FakeTransport(); + // pingInterval 0 makes the keepalive due on every check. + $conn = $this->connect($fake, ['pingInterval' => 0.0]); + + $before = $fake->written; + $conn->tick(); + + $this->assertStringContainsString( + "PING\r\n", + substr($fake->written, \strlen($before)), + 'tick() must drive the keepalive without a message being read', + ); + + $conn->close(); + } + + public function testTickRecyclesAConnectionThatStoppedAnsweringPings(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'pingInterval' => 0.0, + 'maxPingsOut' => 1, + ]); + + // Counted off the wire rather than from a callback: a recycled + // connection re-runs the handshake, so a fresh CONNECT is the effect + // worth asserting on. + $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $this->assertSame(1, $handshakes()); + + // The handshake needed its PONG; from here the server goes silent. + $fake->answerPings = false; + + // First tick sends the PING and collects no answer; the second sees the + // outstanding ping budget exhausted and recycles. + $conn->tick(); + $this->assertSame(1, $handshakes(), 'One unanswered ping is still within budget'); + + // The same silent server serves the reconnect, so it cannot complete + // either -- which is the honest outcome. A connection that cannot be + // rebuilt must raise rather than report itself healthy. + try { + $conn->tick(); + $this->fail('tick() must not return normally once the connection is dead'); + } catch (ConnectionException) { + // expected + } + + $this->assertGreaterThan( + 1, + $handshakes(), + 'An unanswered keepalive must recycle the connection', + ); + + $conn->close(); + } + + /** + * A publisher must not be able to out-ping itself. + * + * A PING is only half a keepalive: the read path is what clears the + * outstanding count, and a caller that only writes never reads. Emitting + * from the write path therefore accumulated one unanswered PING per + * interval on every write-only connection until the budget was spent, and + * then declared a perfectly healthy socket stale -- the keepalive causing + * the outage it exists to prevent, on a connection the server was watching + * publish the whole time. + * + * The server here answers nothing, standing in for a connection with no + * reader. What is asserted is that publishing alone neither emits a + * keepalive nor rebuilds the connection. + */ + public function testPublishingDoesNotAccumulateUnansweredPings(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'pingInterval' => 0.0, + 'maxPingsOut' => 1, + ]); + + $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $this->assertSame(1, $handshakes()); + + // Nothing will answer from here, exactly as on a connection whose + // holder only ever writes to it. + $fake->answerPings = false; + $before = \strlen($fake->written); + + for ($i = 0; $i < 5; $i++) { + $conn->publish('foo', 'bar'); + } + + $written = substr($fake->written, $before); + + $this->assertStringNotContainsString( + "PING\r\n", + $written, + 'The write path must take the keepalive verdict without emitting a PING nothing will collect', + ); + $this->assertSame( + 1, + $handshakes(), + 'A busy publisher must not reconnect: the server can see its traffic, so the socket is not idle', + ); + $this->assertTrue($conn->isConnected()); + + $conn->close(); + } + + + public function testTickCollectsItsOwnPongsSoAHealthyConnectionSurvives(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'pingInterval' => 0.0, + 'maxPingsOut' => 2, + ]); + + $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + + // checkPings() only writes. Nothing else clears the outstanding count + // on a connection whose holder never reads -- a pooled publisher -- so + // without collecting its own PONGs the keepalive would march to + // maxPingsOut and declare a live server stale. Far more ticks than the + // budget, against a server answering every one of them. + for ($i = 0; $i < 20; $i++) { + $conn->tick(); + } + + $this->assertSame( + 1, + $handshakes(), + 'A server answering every ping must never be recycled, however long the connection idles', + ); + + // And it is still usable rather than merely un-recycled. + $conn->publish('subject', 'payload'); + $this->assertStringContainsString('PUB subject', $fake->written); + + $conn->close(); + } + + /** + * The write path takes the keepalive verdict before writing -- and only the + * verdict. + * + * This test used to assert that publish() emitted a PING ahead of the PUB, + * which is what the keepalive looked like when it was one method. But a + * PING is only half a keepalive: the read path clears the outstanding + * count, and a caller that only writes never reads, so emitting here made + * every write-only connection accumulate one unanswered PING per interval + * until the budget was spent and a healthy socket was declared stale. A + * connection that is actively publishing is not idle either -- the server + * can see its traffic -- so there was nothing for that PING to keep alive. + * + * What has to survive is the ordering intent: a connection already known to + * be dead must be recycled *before* the payload is written, so a publish + * never lands on a socket we have already given up on. That is asserted + * here, and {@see self::testPublishingDoesNotAccumulateUnansweredPings()} + * pins the half that was removed. + */ + public function testPublishTakesTheKeepaliveVerdictBeforeWriting(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake, [ + 'pingInterval' => 0.0, + 'maxPingsOut' => 1, + ]); + + $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + + // Spend the budget from the read side, which is where it is spent in + // practice: a PING the now-silent server will never answer. + $fake->answerPings = false; + $conn->tick(); + $this->assertSame(1, $handshakes()); + + $offset = \strlen($fake->written); + + try { + $conn->publish('foo', 'bar'); + $this->fail('A publish onto a spent keepalive budget must not report success'); + } catch (ConnectionException) { + // The reconnect is served by the same silent server, so it cannot + // complete -- which is the honest outcome. + } + + $this->assertGreaterThan( + 1, + $handshakes(), + 'The write path must act on a connection whose keepalive budget is spent', + ); + $this->assertStringNotContainsString( + 'PUB foo', + substr($fake->written, $offset), + 'The payload must not reach a socket the keepalive had already condemned', + ); + } + + // --- Finding: transports must not raise TypeError on a closed stream --- + /** + * @return \Iterator, array{class-string}> + */ + public static function streamTransportProvider(): \Iterator + { + yield [TcpTransport::class]; + yield [TlsTransport::class]; + yield [WebSocketTransport::class]; + } + + /** + * A close() concurrent with a yielded fread/fwrite leaves a closed resource + * behind. Every stream function raises TypeError on one, and TypeError is an + * \Error -- so it bypasses the ConnectionException catches that drive + * reconnection. The transports must report it as a connection failure. + * + * @param class-string $class + */ + #[DataProvider('streamTransportProvider')] + public function testClosedStreamRaisesConnectionExceptionNotTypeError(string $class): void + { + $transport = $this->transportWithClosedStream($class); + + $this->expectException(ConnectionException::class); + $transport->write("PING\r\n"); + } + + /** + * @param class-string $class + */ + #[DataProvider('streamTransportProvider')] + public function testClosedStreamReadsRaiseConnectionExceptionNotTypeError(string $class): void + { + $transport = $this->transportWithClosedStream($class); + + $this->expectException(ConnectionException::class); + $transport->read(64); + } + + /** + * @param class-string $class + */ + #[DataProvider('streamTransportProvider')] + public function testClosedStreamIsNotReportedAsConnected(string $class): void + { + $transport = $this->transportWithClosedStream($class); + + // Previously feof() on the closed resource raised TypeError here. + $this->assertFalse($transport->isConnected()); + } + + /** + * The same defect, one call deeper: mid-loop rather than on entry. + * + * fwrite() performs short writes, so write() loops -- and under Swoole's + * stream hooks each fwrite() is a yield point, which makes the gap between + * two iterations a window a close() can land in. The loop captured its + * handle once before the first pass, so the next iteration wrote to the + * stale one and raised TypeError: an \Error, so it bypassed the + * ConnectionException catches that drive reconnection, exactly as the + * on-entry case did before it was fixed. + * + * A stream wrapper supplies the short write and the seam in one: PHP calls + * stream_write() repeatedly inside a single fwrite(), so accepting one byte + * and then refusing makes fwrite() report a genuine partial write, and the + * callback fired on the way past swaps the property for the closed handle a + * concurrent close() would leave behind. + * + * Both versions raise ConnectionException here, so the message is what + * tells them apart -- and it is the real distinction, not an incidental + * one. A loop that re-reads the property reports the connection gone; one + * writing to the copy it captured gets as far as the socket and blames the + * write. Before the fix there was no third outcome only because this test + * cannot run PHP's stream functions against a genuinely closed resource + * without them raising TypeError, which is the production symptom. + * + * @param class-string $class + */ + #[DataProvider('streamTransportProvider')] + public function testStreamClosedMidWriteRaisesConnectionExceptionNotTypeError(string $class): void + { + $transport = new $class(); + $property = new \ReflectionProperty($class, 'stream'); + + $stream = fopen('utopia-shortwrite://mid-loop', 'r+'); + $this->assertIsResource($stream); + $property->setValue($transport, $stream); + + ShortWriteStream::$afterFirstWrite = function () use ($transport, $property): void { + $dead = fopen('php://temp', 'r+'); + fclose($dead); + $property->setValue($transport, $dead); + }; + + try { + $transport->write("PING\r\n"); + $this->fail('A write onto a stream closed mid-loop must not report success'); + } catch (ConnectionException $e) { + $this->assertStringContainsString( + 'Not connected', + $e->getMessage(), + 'The loop must re-read the stream property, not write to the handle it captured', + ); + } finally { + ShortWriteStream::$afterFirstWrite = null; + } + } + + /** + * Build a transport holding a stream that has been closed underneath it, + * standing in for a close() that landed during a yielded socket call. + * + * @param class-string $class + */ + private function transportWithClosedStream(string $class): object + { + $transport = new $class(); + + $stream = fopen('php://temp', 'r+'); + $this->assertIsResource($stream); + + $property = new \ReflectionProperty($class, 'stream'); + $property->setValue($transport, $stream); + + fclose($stream); + + return $transport; + } +} + +/** + * A stream that accepts one byte per write, so a transport's short-write loop is + * guaranteed more than one pass, and that runs a callback after the first of + * them -- the seam a concurrent close() would land in. + */ +final class ShortWriteStream +{ + /** Fired once, after the first write, to stand in for the racing close(). */ + public static ?\Closure $afterFirstWrite = null; + + /** @var resource|null Set by PHP for stream context; unused here. */ + public $context; + + private int $writes = 0; + + public function stream_open(): bool + { + return true; + } + + public function stream_write(string $data): int + { + $this->writes++; + + // PHP drives stream_write() in a loop inside one fwrite() call until + // everything is accepted, so accepting a byte and then refusing is what + // makes fwrite() itself return short -- which is the only thing the + // transport's own loop responds to. + if ($this->writes > 1) { + return 0; + } + + if (self::$afterFirstWrite instanceof \Closure) { + $callback = self::$afterFirstWrite; + self::$afterFirstWrite = null; + $callback(); + } + + return min(1, \strlen($data)); + } + + public function stream_eof(): bool + { + return false; + } + + /** @return array */ + public function stream_stat(): array + { + return []; + } + + // PHP passes three arguments; none of them matter to a stream that only has + // to accept a timeout being set on it. + public function stream_set_option(): bool + { + return true; + } +} diff --git a/tests/Unit/ConsumerFetchLifecycleTest.php b/tests/Unit/ConsumerFetchLifecycleTest.php new file mode 100644 index 00000000000..e791ddd2308 --- /dev/null +++ b/tests/Unit/ConsumerFetchLifecycleTest.php @@ -0,0 +1,138 @@ + $extra */ + private function connect(FakeTransport $fake, array $extra = []): Connection + { + return Connection::connect(new ConnectionOptions(...array_merge([ + 'servers' => 'nats://127.0.0.1:4222', + 'allowReconnect' => false, + 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + ], $extra))); + } + + private function consumer(Connection $conn): Consumer + { + return new Consumer($conn, 'STREAM', ConsumerInfo::fromArray([ + 'stream_name' => 'STREAM', + 'name' => 'durable', + ])); + } + + /** + * @return array + */ + private function subscriptions(Connection $conn): array + { + $property = new \ReflectionProperty(Connection::class, 'subscriptions'); + + /** @var array $subs */ + $subs = $property->getValue($conn); + + return $subs; + } + + public function testFetchReleasesItsInboxSubscription(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $before = \count($this->subscriptions($conn)); + $consumer->fetch(1, 0.02); + + $this->assertCount( + $before, + $this->subscriptions($conn), + 'The inbox subscription must be released on the success path', + ); + } + + public function testFetchReleasesItsInboxSubscriptionWhenItThrows(): void + { + // A tiny max_payload makes the pull request itself unpublishable, so the + // throw lands after the inbox subscription is already open. + $fake = new FakeTransport(['max_payload' => 4]); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $before = \count($this->subscriptions($conn)); + + try { + $consumer->fetch(1, 0.02); + $this->fail('Expected the oversized pull request to raise'); + } catch (MaxPayloadException) { + // expected + } + + $this->assertCount( + $before, + $this->subscriptions($conn), + 'A throw must not leak the inbox subscription', + ); + } + + public function testRepeatedFailedFetchesDoNotAccumulateSubscriptions(): void + { + $fake = new FakeTransport(['max_payload' => 4]); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $before = \count($this->subscriptions($conn)); + + for ($i = 0; $i < 5; $i++) { + try { + $consumer->fetch(1, 0.02); + } catch (MaxPayloadException) { + // expected + } + } + + $this->assertCount( + $before, + $this->subscriptions($conn), + 'Subscription count must stay flat across repeated failures', + ); + } + + public function testServerPullExpiryLandsBeforeTheClientDeadline(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $timeout = 0.05; + $consumer->fetch(1, $timeout); + + $this->assertMatchesRegularExpression('/\{"batch":1,"expires":\d+\}/', $fake->written); + preg_match('/\{"batch":1,"expires":(\d+)\}/', $fake->written, $matches); + $expires = (int) $matches[1]; + + $this->assertGreaterThan(0, $expires, 'The server still needs a usable window'); + $this->assertLessThan( + StreamConfig::secondsToNanos($timeout), + $expires, + 'A message dispatched at a shared boundary is dropped client-side while the server counts the delivery', + ); + } +} diff --git a/tests/Unit/ParserFrameIntegrityTest.php b/tests/Unit/ParserFrameIntegrityTest.php new file mode 100644 index 00000000000..445665638ae --- /dev/null +++ b/tests/Unit/ParserFrameIntegrityTest.php @@ -0,0 +1,85 @@ +pushInbound("MSG foo 1 5\r\nhello\r\n"); + + [$op, $data] = $parser->next(1.0); + + $this->assertSame(ServerOp::Msg, $op); + $this->assertSame('hello', $data['payload']); + $this->assertSame('foo', $data['subject']); + } + + public function testMidFrameTimeoutIsNotReportedAsATimeout(): void + { + $fake = new FakeTransport(); + $parser = new Parser($fake); + // Header announces five payload bytes that never arrive. + $fake->pushInbound("MSG foo 1 5\r\n"); + + try { + $parser->next(0.01); + $this->fail('Expected the mid-frame failure to raise'); + } catch (TimeoutException) { + $this->fail('A mid-frame failure must not surface as a timeout: the caller would read it as "nothing arrived" and carry on against a desynced stream'); + } catch (ConnectionException $e) { + $this->assertStringContainsString('desynced', $e->getMessage()); + } + } + + public function testPoisonedParserRefusesEvenValidFollowingData(): void + { + $fake = new FakeTransport(); + $parser = new Parser($fake); + $fake->pushInbound("MSG foo 1 5\r\n"); + + try { + $parser->next(0.01); + } catch (ConnectionException) { + // expected; the parser is now poisoned + } + + // Whatever arrives next cannot be trusted to start on a frame boundary, + // so the parser must keep refusing until the connection is rebuilt. + $fake->pushInbound("PING\r\n"); + + $this->expectException(ConnectionException::class); + $parser->next(1.0); + } + + public function testMidFrameFailureSurfacesTheOriginalCause(): void + { + $fake = new FakeTransport(); + $parser = new Parser($fake); + $fake->pushInbound("HMSG foo 1 12 20\r\n"); + + try { + $parser->next(0.01); + $this->fail('Expected the mid-frame failure to raise'); + } catch (ConnectionException $e) { + $this->assertInstanceOf(TimeoutException::class, $e->getPrevious()); + } + } +} diff --git a/tests/Unit/Support/FakeTransport.php b/tests/Unit/Support/FakeTransport.php index 28d75efd151..65db3e326c6 100644 --- a/tests/Unit/Support/FakeTransport.php +++ b/tests/Unit/Support/FakeTransport.php @@ -22,6 +22,13 @@ final class FakeTransport implements Transport /** @var list> */ public array $tlsUpgrades = []; + /** + * Whether the server answers PINGs. Set false to emulate a server that has + * gone away without closing the socket -- the case the stale-connection + * budget exists for. + */ + public bool $answerPings = true; + private string $inbound = ''; private bool $connected = false; @@ -40,9 +47,11 @@ public function write(string $data): int $this->writes[] = $data; // Answer PINGs so the handshake / flush / drain barrier completes. - $pings = substr_count($data, "PING\r\n"); - for ($i = 0; $i < $pings; $i++) { - $this->inbound .= "PONG\r\n"; + if ($this->answerPings) { + $pings = substr_count($data, "PING\r\n"); + for ($i = 0; $i < $pings; $i++) { + $this->inbound .= "PONG\r\n"; + } } return \strlen($data); diff --git a/tests/Unit/WriteFailureSemanticsTest.php b/tests/Unit/WriteFailureSemanticsTest.php new file mode 100644 index 00000000000..c3b5fdd4893 --- /dev/null +++ b/tests/Unit/WriteFailureSemanticsTest.php @@ -0,0 +1,92 @@ +publish() waits for a + * PubAck that never arrives and raises TimeoutException. + */ + private function unresponsiveConnection(FakeTransport $fake): Connection + { + return Connection::connect(new ConnectionOptions( + servers: 'nats://127.0.0.1:4222', + allowReconnect: false, + requestTimeout: 0.02, + transportFactory: fn(string $scheme): FakeTransport => $fake, + )); + } + + public function testKeyValueCreatePropagatesATransportFailure(): void + { + $conn = $this->unresponsiveConnection(new FakeTransport()); + $kv = new KeyValue($conn, $conn->jetStream(), 'BUCKET'); + + // Previously rewritten to KeyValueException('Key already exists'), which + // tells the caller the write is settled when in fact it is unknown. + $this->expectException(TimeoutException::class); + $kv->create('key', 'value'); + } + + public function testKeyValueUpdatePropagatesATransportFailure(): void + { + $conn = $this->unresponsiveConnection(new FakeTransport()); + $kv = new KeyValue($conn, $conn->jetStream(), 'BUCKET'); + + // Previously rewritten to KeyValueException('Wrong last revision'). + $this->expectException(TimeoutException::class); + $kv->update('key', 'value', 7); + } + + public function testKeyValueTransportFailureIsNotAKeyValueException(): void + { + $conn = $this->unresponsiveConnection(new FakeTransport()); + $kv = new KeyValue($conn, $conn->jetStream(), 'BUCKET'); + + try { + $kv->create('key', 'value'); + $this->fail('Expected the transport failure to raise'); + } catch (KeyValueException $e) { + $this->fail('A transport failure must not be reported as a CAS verdict: ' . $e->getMessage()); + } catch (TimeoutException $e) { + $this->assertStringContainsString('Request timed out', $e->getMessage()); + } + } + + public function testObjectStorePutPropagatesATransportFailure(): void + { + $conn = $this->unresponsiveConnection(new FakeTransport()); + $store = new ObjectStore($conn, $conn->jetStream(), 'BUCKET'); + + // An empty object writes no chunks, so the first publish attempted is the + // guarded meta publish -- the path that used to purge on any failure. + try { + $store->put('object', ''); + $this->fail('Expected the transport failure to raise'); + } catch (ObjectStoreException $e) { + $this->fail('A transport failure must not be reported as a conflict, and must not purge chunks: ' . $e->getMessage()); + } catch (TimeoutException $e) { + $this->assertStringContainsString('Request timed out', $e->getMessage()); + } + } +} From 0d9db5ee7754f382ade9dec9a2b9fafee822ec20 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:55:48 +0200 Subject: [PATCH 379/470] Merge pull request #189 from utopia-php/fix/nats-requestmany-stall-timing test(nats): measure which deadline stopped requestMany, not the runner --- tests/E2E/RequestManyTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/E2E/RequestManyTest.php b/tests/E2E/RequestManyTest.php index 79236fa4a57..d5b5befaede 100644 --- a/tests/E2E/RequestManyTest.php +++ b/tests/E2E/RequestManyTest.php @@ -87,12 +87,20 @@ public function testStallStopsBeforeTimeout(): void // Three fast replies then silence. A short stall window returns well // before the generous overall timeout would. + // + // The overall timeout is wide and the bound is set between the two, so + // this measures which of them stopped collection rather than how fast + // the runner is. The responders share the requester's connection, so + // the single loop has to carry the request out and three replies back + // through nextMessage() rounds of one stall window each; on a loaded + // runner that is comfortably over a second, while a stall that never + // fired would take the full ten. $start = microtime(true); - $replies = $conn->requestMany($subject, 'ping', ['timeout' => 3.0, 'stall' => 0.3]); + $replies = $conn->requestMany($subject, 'ping', ['timeout' => 10.0, 'stall' => 0.3]); $elapsed = microtime(true) - $start; $this->assertCount(3, $replies); - $this->assertLessThan(1.5, $elapsed); + $this->assertLessThan(5.0, $elapsed); $conn->close(); } From 2eed4679bc307816aa66360fa41dd826aefef2aa Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:18:25 +0200 Subject: [PATCH 380/470] Merge pull request #192 from utopia-php/fix/nats-pull-request-and-nodelay fix(nats): three defects on the JetStream pull path --- src/JetStream/Consumer.php | 45 ++-- src/Transport/TcpTransport.php | 5 + tests/E2E/ConsumerNoWaitTest.php | 152 ++++++++++++ tests/Unit/ConsumerPullRequestTest.php | 221 ++++++++++++++++++ .../Transport/TcpTransportNoDelayTest.php | 82 +++++++ 5 files changed, 490 insertions(+), 15 deletions(-) create mode 100644 tests/E2E/ConsumerNoWaitTest.php create mode 100644 tests/Unit/ConsumerPullRequestTest.php create mode 100644 tests/Unit/Transport/TcpTransportNoDelayTest.php diff --git a/src/JetStream/Consumer.php b/src/JetStream/Consumer.php index b6dfca18fe9..01c821d84d7 100644 --- a/src/JetStream/Consumer.php +++ b/src/JetStream/Consumer.php @@ -32,18 +32,23 @@ public function fetch(int $batch = 1, ?float $timeout = null, bool $noWait = fal $timeout ??= 5.0; $requestSubject = "{$this->apiPrefix}.CONSUMER.MSG.NEXT.{$this->stream}.{$this->getName()}"; - // Expire the server's pull request just before the client stops waiting. - // On an identical deadline a message the server dispatches at the - // boundary is dropped here while the server has already counted the - // delivery, burning an attempt against maxDeliver for nothing. - $serverExpiry = $timeout - min(self::FETCH_EXPIRY_MARGIN, $timeout * 0.1); - - $request = [ - 'batch' => $batch, - 'expires' => StreamConfig::secondsToNanos($serverExpiry), - ]; + $request = ['batch' => $batch]; if ($noWait) { + // No 'expires' alongside it. A pull request that carries an expiry + // waits the whole window and then answers 408 Request Timeout, so + // no_wait does nothing at all; sent on its own it comes back 404 No + // Messages immediately, which is the only reason to ask for it. A + // caller polling an empty consumer therefore paid the full timeout + // per call -- 0.25s of poll is a ceiling of four calls a second. $request['no_wait'] = true; + } else { + // Expire the server's pull request just before the client stops + // waiting. On an identical deadline a message the server dispatches + // at the boundary is dropped here while the server has already + // counted the delivery, burning an attempt against maxDeliver for + // nothing. + $serverExpiry = $timeout - min(self::FETCH_EXPIRY_MARGIN, $timeout * 0.1); + $request['expires'] = StreamConfig::secondsToNanos($serverExpiry); } if ($maxBytes !== null) { $request['max_bytes'] = $maxBytes; @@ -92,19 +97,29 @@ private function collectBatch( break; } - // Check for status messages (408 = Request Timeout, 404 = No Messages, 409 = Leadership Change) if ($msg->headers instanceof \Utopia\NATS\Headers) { $status = $msg->headers->getStatus(); - if (\in_array($status, ['408', '404', '409'], true)) { - break; - } - // 100 = flow control / idle heartbeat: keep-alive, not data. + + // 100 = flow control / idle heartbeat: keep-alive, not data, and + // the only status that means "carry on waiting". if ($status === '100') { if ($msg->replyTo !== null && $msg->replyTo !== '') { $this->conn->publish($msg->replyTo, ''); } continue; } + + // Every other status ends this pull request. Naming the expected + // codes (404/408/409) and falling through on the rest handed the + // unnamed ones back as data: a 503 No Responders -- which the + // server sends while a consumer is still being created or a raft + // leader is moving -- became a message with an empty body, and a + // caller that json_decode()s the payload got null and died on it. + // A status frame carries no payload whatever its number, so it can + // never be a message. + if ($status !== '') { + break; + } } $messageBatch->addMessage($msg); diff --git a/src/Transport/TcpTransport.php b/src/Transport/TcpTransport.php index 0d824260a71..03d80b34086 100644 --- a/src/Transport/TcpTransport.php +++ b/src/Transport/TcpTransport.php @@ -24,6 +24,11 @@ public function connect(string $host, int $port, float $timeout): void $errstr, $timeout, STREAM_CLIENT_CONNECT, + // Nagle off. Every request-reply here writes the inbox SUB and then + // the request PUB before reading, and Nagle holds that second write + // until the peer's delayed-ACK timer fires -- around 40ms per + // exchange, on loopback as readily as over a network. + stream_context_create(['socket' => ['tcp_nodelay' => true]]), ); if ($stream === false) { diff --git a/tests/E2E/ConsumerNoWaitTest.php b/tests/E2E/ConsumerNoWaitTest.php new file mode 100644 index 00000000000..1a310d39d6e --- /dev/null +++ b/tests/E2E/ConsumerNoWaitTest.php @@ -0,0 +1,152 @@ + */ + private array $streams = []; + + private function getServerUrl(): string + { + return getenv('NATS_URL') ?: 'nats://127.0.0.1:4222'; + } + + protected function setUp(): void + { + $this->conn = Connection::connect($this->getServerUrl()); + $this->js = $this->conn->jetStream(); + } + + protected function tearDown(): void + { + foreach ($this->streams as $stream) { + try { + $this->js->deleteStream($stream); + } catch (\Throwable) { + // already gone + } + } + $this->conn->close(); + } + + /** @return array{0: string, 1: string} stream name and subject */ + private function createStream(): array + { + $name = 'NW_' . uniqid(); + $subject = "nowait.{$name}"; + + $this->js->createStream(new StreamConfig( + name: $name, + subjects: [$subject], + storage: StorageType::Memory, + )); + $this->streams[] = $name; + + return [$name, $subject]; + } + + public function testNoWaitFetchOnAnEmptyConsumerReturnsWithoutWaitingOutTheTimeout(): void + { + [$stream, $subject] = $this->createStream(); + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'nowait', + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + )); + + // A deliberately long timeout, so "returned promptly" cannot be confused + // with "the timeout happened to be short". + $timeout = 2.0; + $started = microtime(true); + $batch = $consumer->fetch(1, $timeout, true); + $elapsed = microtime(true) - $started; + + $this->assertCount(0, $batch, 'The consumer is empty, so nothing comes back'); + $this->assertLessThan( + $timeout / 4, + $elapsed, + \sprintf( + 'A no_wait fetch on an empty consumer took %.3fs of a %.1fs timeout; it is asking the ' + . 'server to answer immediately, so it must not track the timeout', + $elapsed, + $timeout, + ), + ); + } + + public function testNoWaitFetchStillReturnsAMessageThatIsWaiting(): void + { + [$stream, $subject] = $this->createStream(); + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'nowait', + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + )); + + $this->js->publish($subject, 'payload'); + + $batch = $consumer->fetch(1, 2.0, true); + + $this->assertCount(1, $batch, 'Answering immediately must not mean answering empty'); + foreach ($batch as $message) { + $this->assertSame('payload', $message->getData()); + $message->ack(); + } + } + + public function testWaitingFetchStillHonoursItsTimeoutOnAnEmptyConsumer(): void + { + [$stream, $subject] = $this->createStream(); + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'waiting', + ackPolicy: AckPolicy::Explicit, + filterSubject: $subject, + )); + + // The complement of the first test: without no_wait the caller asked to + // be held, and dropping the expiry from that request would leave the + // server holding it for its own default instead of the one given. + $timeout = 0.5; + $started = microtime(true); + $batch = $consumer->fetch(1, $timeout, false); + $elapsed = microtime(true) - $started; + + $this->assertCount(0, $batch); + $this->assertGreaterThanOrEqual( + $timeout * 0.5, + $elapsed, + 'A waiting fetch must actually wait', + ); + $this->assertLessThan( + $timeout * 3, + $elapsed, + 'A waiting fetch must return near its own timeout, not the server default', + ); + } +} diff --git a/tests/Unit/ConsumerPullRequestTest.php b/tests/Unit/ConsumerPullRequestTest.php new file mode 100644 index 00000000000..05af1d11701 --- /dev/null +++ b/tests/Unit/ConsumerPullRequestTest.php @@ -0,0 +1,221 @@ + $fake, + )); + } + + private function consumer(Connection $conn): Consumer + { + return new Consumer($conn, 'STREAM', ConsumerInfo::fromArray([ + 'stream_name' => 'STREAM', + 'name' => 'durable', + ])); + } + + /** + * The JSON body of the pull request the client published. + * + * @return array + */ + private function pullRequest(FakeTransport $fake): array + { + $matched = preg_match( + '#PUB \$JS\.API\.CONSUMER\.MSG\.NEXT\.STREAM\.durable \S+ \d+\r\n(\{.*?\})\r\n#s', + $fake->written, + $m, + ); + $this->assertSame(1, $matched, 'No pull request was published'); + + /** @var array $decoded */ + $decoded = json_decode($m[1], true, 512, JSON_THROW_ON_ERROR); + + return $decoded; + } + + /** + * The sid fetch() is about to give its inbox subscription. + * + * Inbound frames are routed by sid, and a reply has to be queued on the fake + * before the synchronous fetch() starts reading -- by which point the inbox + * subject it generated is not observable yet. The sid is, because it comes + * from a counter. + */ + private function pendingSid(Connection $conn): string + { + return (string) (new \ReflectionProperty(Connection::class, 'nextSid'))->getValue($conn); + } + + /** Queue a status frame on the inbox fetch() is about to open. */ + private function pushStatus(FakeTransport $fake, string $sid, string $status, string $replyTo = ''): void + { + $block = "NATS/1.0 {$status}\r\n\r\n"; + $length = \strlen($block); + $reply = $replyTo === '' ? '' : " {$replyTo}"; + + $fake->pushInbound("HMSG _INBOX.scripted {$sid}{$reply} {$length} {$length}\r\n{$block}\r\n"); + } + + public function testNoWaitPullRequestCarriesNoExpiry(): void + { + // The server honours the expiry over no_wait: given both, it waits out + // the window and answers 408 instead of answering 404 at once, which + // makes a no_wait poll cost the full timeout it was meant to avoid. + $fake = new FakeTransport(); + $consumer = $this->consumer($this->connect($fake)); + + $consumer->fetch(1, 0.02, true); + + $request = $this->pullRequest($fake); + + $this->assertTrue($request['no_wait'] ?? null); + $this->assertArrayNotHasKey('expires', $request); + } + + public function testWaitingPullRequestStillCarriesAnExpiry(): void + { + // The other half of the same decision: without no_wait the server needs + // the expiry, or it holds the request for its own default. It lands + // just inside the client deadline -- see + // ConsumerFetchLifecycleTest::testServerPullExpiryLandsBeforeTheClientDeadline + // for why the two must not share a boundary. + $fake = new FakeTransport(); + $consumer = $this->consumer($this->connect($fake)); + + $consumer->fetch(1, 0.02); + + $request = $this->pullRequest($fake); + + $this->assertArrayNotHasKey('no_wait', $request); + $this->assertSame(18_000_000, $request['expires']); + } + + public function testBatchSizeAndMaxBytesSurviveTheNoWaitPath(): void + { + $fake = new FakeTransport(); + $consumer = $this->consumer($this->connect($fake)); + + $consumer->fetch(7, 0.02, true, 4096); + + $request = $this->pullRequest($fake); + + $this->assertSame(7, $request['batch']); + $this->assertSame(4096, $request['max_bytes']); + $this->assertTrue($request['no_wait'] ?? null); + $this->assertArrayNotHasKey('expires', $request); + } + + /** + * A 503 is what the server sends when the consumer's API subject has no + * responder at that instant -- a consumer still being created, or a raft + * leader moving. It was not among the codes fetch() named, so it fell + * through and was returned as a message with an empty body. + */ + public function testNoRespondersStatusIsNotReturnedAsAMessage(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $this->pushStatus($fake, $this->pendingSid($conn), '503 No Responders'); + + $batch = $consumer->fetch(1, 0.02, true); + + $this->assertCount(0, $batch, 'A 503 status frame is not a message'); + } + + /** + * The same for a status nobody has enumerated. The rule has to be "a status + * frame is not data", not a longer list of numbers, or the next code the + * server adds is returned as a job all over again. + */ + public function testAnUnrecognisedStatusIsNotReturnedAsAMessage(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $this->pushStatus($fake, $this->pendingSid($conn), '599 Something New'); + + $batch = $consumer->fetch(1, 0.02, true); + + $this->assertCount(0, $batch); + } + + public function testNoMessagesStatusEndsTheFetchWithoutAMessage(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $this->pushStatus($fake, $this->pendingSid($conn), '404 No Messages'); + + $batch = $consumer->fetch(1, 0.02, true); + + $this->assertCount(0, $batch); + } + + /** + * 100 is the one status that means carry on: it is a keep-alive, so it must + * not end the fetch, and a real message behind it still has to arrive. + */ + public function testFlowControlStatusIsAcknowledgedAndDoesNotEndTheFetch(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $sid = $this->pendingSid($conn); + $this->pushStatus($fake, $sid, '100 FlowControl Request', '_FC.reply'); + $fake->pushInbound("MSG _INBOX.scripted {$sid} 5\r\nhello\r\n"); + + $batch = $consumer->fetch(1, 0.5, true); + + $this->assertCount(1, $batch, 'A keep-alive must not end the fetch'); + foreach ($batch as $message) { + $this->assertSame('hello', $message->getData()); + } + $this->assertStringContainsString('PUB _FC.reply', $fake->written, 'The keep-alive is answered'); + } + + public function testAMessageWithoutAStatusIsStillDelivered(): void + { + $fake = new FakeTransport(); + $conn = $this->connect($fake); + $consumer = $this->consumer($conn); + + $fake->pushInbound("MSG _INBOX.scripted {$this->pendingSid($conn)} 7\r\npayload\r\n"); + + $batch = $consumer->fetch(1, 0.5, true); + + $this->assertCount(1, $batch); + foreach ($batch as $message) { + $this->assertSame('payload', $message->getData()); + } + } +} diff --git a/tests/Unit/Transport/TcpTransportNoDelayTest.php b/tests/Unit/Transport/TcpTransportNoDelayTest.php new file mode 100644 index 00000000000..58b34c05484 --- /dev/null +++ b/tests/Unit/Transport/TcpTransportNoDelayTest.php @@ -0,0 +1,82 @@ +markTestSkipped('Reading a socket option back needs ext-sockets'); + } + + $server = @stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr); + if ($server === false) { + $this->markTestSkipped("Could not open a loopback listener: [{$errno}] {$errstr}"); + } + + $this->server = $server; + $this->address = (string) stream_socket_get_name($server, false); + } + + protected function tearDown(): void + { + if (\is_resource($this->server)) { + fclose($this->server); + } + } + + public function testConnectedSocketHasNagleDisabled(): void + { + [$host, $port] = explode(':', $this->address); + + $transport = new TcpTransport(); + $transport->connect($host, (int) $port, 2.0); + + try { + $this->assertTrue($transport->isConnected()); + + $property = new \ReflectionProperty(TcpTransport::class, 'stream'); + $stream = $property->getValue($transport); + $this->assertIsResource($stream); + + // socket_import_stream shares the underlying descriptor, so this reads + // the option actually set on the connected socket rather than a copy of + // whatever the context asked for. + $socket = socket_import_stream($stream); + if ($socket === false) { + $this->markTestSkipped('This platform cannot import a stream as a socket'); + } + + // Asserted as "not off" rather than a literal: the value read back is + // platform-specific (1 on Linux, 4 on macOS) while 0 is off everywhere. + $this->assertNotSame( + 0, + socket_get_option($socket, SOL_TCP, TCP_NODELAY), + 'TCP_NODELAY must be set, or every request-reply stalls on the peer delayed ACK', + ); + } finally { + $transport->close(); + } + } +} From 1a51a376bc5a486dce74af12589f53ddfe5afcbc Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 2 Sep 2026 08:57:19 -0700 Subject: [PATCH 381/470] Merge pull request #194 from utopia-php/chore/no-package-lock-files chore: stop committing package lock files --- .gitignore | 1 + composer.lock | 21 --------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 958e783be05..734074d299a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ composer.phar .DS_Store Thumbs.db .env +composer.lock diff --git a/composer.lock b/composer.lock deleted file mode 100644 index f757b4b141a..00000000000 --- a/composer.lock +++ /dev/null @@ -1,21 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "cfb25afa49eaafa76acee2a192910511", - "packages": [], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": true, - "prefer-lowest": false, - "platform": { - "php": ">=8.1", - "ext-json": "*" - }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} From 0707574e6df044fb4029c4e9889f7334ac976deb Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:50:29 +0200 Subject: [PATCH 382/470] Merge pull request #229 from utopia-php/fix/nats-publish-many feat(nats,queue): batch JetStream publishes instead of one round trip per message --- README.md | 22 +++ src/Connection.php | 5 + src/Headers.php | 8 +- src/JetStream/JetStream.php | 186 ++++++++++++++++++++++--- tests/E2E/JetStreamPublishManyTest.php | 159 +++++++++++++++++++++ 5 files changed, 358 insertions(+), 22 deletions(-) create mode 100644 tests/E2E/JetStreamPublishManyTest.php diff --git a/README.md b/README.md index b12d1ed69cb..09ebee726dc 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,28 @@ $ack = $js->publish('orders.new', '{"id": 1}', msgId: 'order-1'); $ack = $js->publish('orders.new', $data, expectedLastSeq: 42); ``` +### Publishing a batch + +`publish()` waits for each acknowledgment before it sends the next message, so a +batch of `N` messages costs `N` round trips. `publishMany()` writes a window of +messages first and reads their acknowledgments afterwards, which costs one round +trip per window. Each message keeps its own acknowledgment, so deduplication and +per-message errors work the same way. + +```php +$acks = $js->publishMany([ + ['subject' => 'orders.new', 'data' => '{"id": 1}', 'msgId' => 'order-1'], + ['subject' => 'orders.new', 'data' => '{"id": 2}', 'msgId' => 'order-2'], +]); + +// The acknowledgments come back in the order the messages were given. +echo "Seq: {$acks[0]->sequence}\n"; +``` + +The `window` argument sets how many messages stay in flight before their +acknowledgments are collected. A message the server rejects throws, as it does on +`publish()`. + ### Consumers ```php diff --git a/src/Connection.php b/src/Connection.php index b6f722a70e2..698fc754cbc 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -516,6 +516,11 @@ public function getServerInfo(): ServerInfo return $this->serverInfo; } + public function getOptions(): ConnectionOptions + { + return $this->options; + } + public function getStatus(): string { return $this->status; diff --git a/src/Headers.php b/src/Headers.php index aacbd99593f..c20e59b2ea9 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -93,12 +93,16 @@ public function toWire(): string public static function fromWire(string $raw): self { $headers = new self(); - $lines = explode("\r\n", $raw); - if ($lines === []) { + // explode() never returns an empty array -- on '' it returns [''] -- so + // testing the result could not reject an empty block, and one fell through + // to the version check below and was reported as an invalid version. + if ($raw === '') { throw new ProtocolException('Empty header block'); } + $lines = explode("\r\n", $raw); + // Parse status line: "NATS/1.0" or "NATS/1.0 503" or "NATS/1.0 503 No Responders" $statusLine = array_shift($lines); if (!str_starts_with($statusLine, 'NATS/1.0')) { diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 3ac1015229e..1a5719beda0 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -6,9 +6,13 @@ use Utopia\NATS\Connection; use Utopia\NATS\Exception\JetStreamException; +use Utopia\NATS\Exception\NatsException; +use Utopia\NATS\Exception\TimeoutException; use Utopia\NATS\Headers; +use Utopia\NATS\Inbox; use Utopia\NATS\KeyValue\KeyValue; use Utopia\NATS\KeyValue\KeyValueConfig; +use Utopia\NATS\Message; final class JetStream { @@ -230,6 +234,167 @@ public function deleteMessage(string $stream, int $seq, bool $noErase = false): */ public function publish(string $subject, string $data = '', ?Headers $headers = null, ?string $msgId = null, ?string $expectedLastMsgId = null, ?int $expectedLastSeq = null, ?int $expectedLastSubjectSeq = null, ?string $expectedStream = null, int|string|null $ttl = null, int $retryOnNoResponders = 0): PubAck { + $useHeaders = $this->publishHeaders( + $headers, + $msgId, + $expectedLastMsgId, + $expectedLastSeq, + $expectedLastSubjectSeq, + $expectedStream, + $ttl, + ); + + $attempt = 0; + while (true) { + try { + $response = $this->conn->request($subject, $data, headers: $useHeaders); + break; + } catch (\Utopia\NATS\Exception\NatsException $e) { + if ($attempt >= $retryOnNoResponders || $e->getMessage() !== 'No responders for request') { + throw $e; + } + $attempt++; + usleep(50_000 * $attempt); + } + } + + $responseData = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); + self::checkError($responseData); + + return PubAck::fromArray($responseData); + } + + /** + * Publish many messages and collect an acknowledgment for each. + * + * publish() pays a round trip per message: it writes one PUB, then blocks on the + * reply before writing the next, so a batch of N messages costs N round trips. + * This writes a window of PUBs first and reads their acknowledgments afterwards, + * which costs one round trip per window. Each message still carries its own reply + * subject and still gets its own acknowledgment, so per-message deduplication and + * per-message errors behave exactly as they do on the single publish. + * + * @param list $messages + * @param float|null $timeout Deadline for one window's acknowledgments, in seconds. + * Defaults to the connection's request timeout. + * @param int $window How many messages may be in flight before their acknowledgments + * are collected. This bounds the reply burst the server sends back + * and keeps it under the subscription's pending limit. + * @return list One entry per message, in the order the messages were given. + */ + public function publishMany(array $messages, ?float $timeout = null, int $window = 256): array + { + if ($messages === []) { + return []; + } + + if ($window < 1) { + throw new \InvalidArgumentException("Publish window must be at least 1, got {$window}"); + } + + $acks = []; + + // Preserved keys carry the caller's index through the windows, so an + // acknowledgment can be placed back where its message came from. + foreach (array_chunk($messages, $window, true) as $chunk) { + foreach ($this->publishWindow($chunk, $timeout) as $index => $ack) { + $acks[$index] = $ack; + } + } + + ksort($acks); + + return array_values($acks); + } + + /** + * Write one window of PUBs, then read back an acknowledgment for every one of them. + * + * @param array> $chunk keyed by the caller's message index + * @return array keyed by the same index + */ + private function publishWindow(array $chunk, ?float $timeout): array + { + $options = $this->conn->getOptions(); + + // Every message replies to its own subject beneath a shared prefix. The server + // acknowledges in whatever order it durably stores the messages, so the trailing + // token is what ties an acknowledgment back to the message that asked for it. + $inbox = Inbox::create($options->inboxPrefix); + $sub = $this->conn->subscribe($inbox . '.*'); + + try { + foreach ($chunk as $index => $message) { + /** @var array{subject: string, data?: string, headers?: Headers, msgId?: string, expectedLastMsgId?: string, expectedLastSeq?: int, expectedLastSubjectSeq?: int, expectedStream?: string, ttl?: int|string} $message */ + $this->conn->publish( + $message['subject'], + $message['data'] ?? '', + $inbox . '.' . $index, + $this->publishHeaders( + $message['headers'] ?? null, + $message['msgId'] ?? null, + $message['expectedLastMsgId'] ?? null, + $message['expectedLastSeq'] ?? null, + $message['expectedLastSubjectSeq'] ?? null, + $message['expectedStream'] ?? null, + $message['ttl'] ?? null, + ), + ); + } + + $deadline = microtime(true) + ($timeout ?? $options->requestTimeout); + $acks = []; + + while (\count($acks) < \count($chunk)) { + $remaining = $deadline - microtime(true); + if ($remaining <= 0) { + break; + } + + $reply = $sub->nextMessage($remaining); + if (!$reply instanceof Message) { + break; + } + + // 503 with no body: JetStream is not answering on this account at all, + // which is the same condition publish() surfaces as "No responders". + if ($reply->headers instanceof Headers && $reply->headers->getStatus() === '503') { + throw new NatsException('No responders for request'); + } + + $responseData = json_decode($reply->data, true, 512, JSON_THROW_ON_ERROR); + self::checkError($responseData); + + $acks[(int) substr($reply->subject, (int) strrpos($reply->subject, '.') + 1)] = PubAck::fromArray($responseData); + } + + if (\count($acks) < \count($chunk)) { + throw new TimeoutException(\sprintf( + 'Timed out waiting for JetStream publish acknowledgments: received %d of %d', + \count($acks), + \count($chunk), + )); + } + + return $acks; + } finally { + $this->conn->unsubscribe($sub); + } + } + + /** + * Assemble the JetStream publish headers, so the single and batched publish paths + * cannot drift on a header name or on how a value is written to the wire. + */ + private function publishHeaders( + ?Headers $headers, + ?string $msgId, + ?string $expectedLastMsgId, + ?int $expectedLastSeq, + ?int $expectedLastSubjectSeq, + ?string $expectedStream, + int|string|null $ttl, + ): ?Headers { $headers ??= new Headers(); if ($msgId !== null) { @@ -251,26 +416,7 @@ public function publish(string $subject, string $data = '', ?Headers $headers = $headers->set('Nats-TTL', \is_int($ttl) ? "{$ttl}s" : $ttl); } - $useHeaders = \count($headers) > 0 ? $headers : null; - - $attempt = 0; - while (true) { - try { - $response = $this->conn->request($subject, $data, headers: $useHeaders); - break; - } catch (\Utopia\NATS\Exception\NatsException $e) { - if ($attempt >= $retryOnNoResponders || $e->getMessage() !== 'No responders for request') { - throw $e; - } - $attempt++; - usleep(50_000 * $attempt); - } - } - - $responseData = json_decode($response->data, true, 512, JSON_THROW_ON_ERROR); - self::checkError($responseData); - - return PubAck::fromArray($responseData); + return \count($headers) > 0 ? $headers : null; } // --- Key-Value --- diff --git a/tests/E2E/JetStreamPublishManyTest.php b/tests/E2E/JetStreamPublishManyTest.php new file mode 100644 index 00000000000..4f995a1efc7 --- /dev/null +++ b/tests/E2E/JetStreamPublishManyTest.php @@ -0,0 +1,159 @@ +conn = Connection::connect(getenv('NATS_URL') ?: 'nats://127.0.0.1:4222'); + $this->js = $this->conn->jetStream(); + + try { + $this->js->deleteStream(self::STREAM); + } catch (\Throwable) { + // not there yet + } + + $this->js->createStream(new StreamConfig( + name: self::STREAM, + subjects: ['publishmany.>'], + duplicateWindow: 120, + )); + } + + protected function tearDown(): void + { + try { + $this->js->deleteStream(self::STREAM); + } catch (\Throwable) { + // already gone + } + $this->conn->close(); + } + + public function testEmptyBatchPublishesNothing(): void + { + $this->assertSame([], $this->js->publishMany([])); + $this->assertSame(0, $this->js->getStreamInfo(self::STREAM)->state->messages); + } + + public function testRejectsAWindowBelowOne(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->js->publishMany([['subject' => 'publishmany.a']], window: 0); + } + + /** + * The acknowledgments come back in whatever order the server stored the + * messages, so this reads each message back out of the stream at the + * sequence its acknowledgment reported. A batch that returned the right + * count while mixing up which acknowledgment belonged to which message + * fails here and passes on a count assertion alone. + */ + public function testEachAcknowledgementBelongsToItsOwnMessage(): void + { + $messages = []; + for ($i = 0; $i < 50; $i++) { + $messages[] = ['subject' => 'publishmany.a', 'data' => "payload-{$i}"]; + } + + // A window smaller than the batch so the collection runs more than once. + $acks = $this->js->publishMany($messages, window: 8); + + $this->assertCount(50, $acks); + + foreach ($acks as $index => $ack) { + $this->assertSame(self::STREAM, $ack->stream); + $this->assertSame( + "payload-{$index}", + $this->js->getMessage(self::STREAM, $ack->sequence)->data, + "acknowledgment {$index} points at sequence {$ack->sequence}, which holds another message", + ); + } + } + + public function testRepublishedMessageIdsComeBackAsDuplicates(): void + { + $messages = []; + for ($i = 0; $i < 10; $i++) { + $messages[] = ['subject' => 'publishmany.a', 'data' => "payload-{$i}", 'msgId' => "id-{$i}"]; + } + + $first = $this->js->publishMany($messages); + foreach ($first as $ack) { + $this->assertFalse($ack->duplicate); + } + + $second = $this->js->publishMany($messages); + foreach ($second as $ack) { + $this->assertTrue($ack->duplicate); + } + + // The duplicates collapsed rather than double-delivering. + $this->assertSame(10, $this->js->getStreamInfo(self::STREAM)->state->messages); + } + + public function testHeadersReachTheStream(): void + { + $headers = new Headers(); + $headers->set('X-Trace', 'abc123'); + + $acks = $this->js->publishMany([ + ['subject' => 'publishmany.a', 'data' => 'with-headers', 'headers' => $headers], + ]); + + $stored = $this->js->getMessage(self::STREAM, $acks[0]->sequence); + $this->assertSame('abc123', $stored->headers?->get('X-Trace')); + } + + /** + * A server-side rejection of one message has to reach the caller. The batch + * writes every message before reading any acknowledgment, so the rejection + * arrives while other messages are still in flight. + */ + public function testARejectedMessageThrows(): void + { + $messages = [ + ['subject' => 'publishmany.a', 'data' => 'fine'], + ['subject' => 'publishmany.a', 'data' => 'rejected', 'expectedStream' => 'NO_SUCH_STREAM'], + ['subject' => 'publishmany.a', 'data' => 'also-fine'], + ]; + + $this->expectException(JetStreamException::class); + $this->js->publishMany($messages); + } + + public function testTheConnectionStaysUsableAfterARejection(): void + { + try { + $this->js->publishMany([ + ['subject' => 'publishmany.a', 'data' => 'rejected', 'expectedStream' => 'NO_SUCH_STREAM'], + ]); + $this->fail('expected the rejection to throw'); + } catch (JetStreamException) { + // the reply subscription is torn down on the way out + } + + $acks = $this->js->publishMany([['subject' => 'publishmany.a', 'data' => 'after']]); + $this->assertSame('after', $this->js->getMessage(self::STREAM, $acks[0]->sequence)->data); + } +} From b6e91ed1264fa861dfd679dd3aff31fce85e71b2 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:44:16 +0530 Subject: [PATCH 383/470] Emit realtime updates for surviving two-way relationship peers --- .../Collections/Documents/Delete.php | 99 +++++++++++++++++++ .../Collections/Documents/Delete.php | 1 + .../Http/TablesDB/Tables/Rows/Delete.php | 1 + .../Collections/Documents/Delete.php | 1 + 4 files changed, 102 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index cc9319a0f4f..86ca2877b03 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -4,6 +4,7 @@ use Appwrite\Databases\TransactionState; use Appwrite\Event\Event; +use Appwrite\Event\Realtime; use Appwrite\Extend\Exception; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; @@ -82,6 +83,7 @@ public function __construct() ->inject('dbForProject') ->inject('getDatabasesDB') ->inject('queueForEvents') + ->inject('queueForRealtime') ->inject('usage') ->inject('transactionState') ->inject('plan') @@ -100,6 +102,7 @@ public function action( Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, + Realtime $queueForRealtime, Context $usage, TransactionState $transactionState, array $plan, @@ -242,6 +245,102 @@ public function action( ->setContext($this->getCollectionsEventsContext(), $collection) ->setPayload($response->output($document, $this->getResponseModel()), sensitive: $relationships); + $this->triggerRelationshipUpdates( + $database, + $collection, + $document, + $dbForProject, + $dbForDatabases, + $queueForEvents, + $queueForRealtime, + $response, + $authorization + ); + $response->noContent(); } + + private function triggerRelationshipUpdates( + Document $database, + Document $collection, + Document $document, + Database $dbForProject, + Database $dbForDatabases, + Event $queueForEvents, + Realtime $queueForRealtime, + UtopiaResponse $response, + Authorization $authorization + ): void { + $processed = []; + $collectionsCache = []; + + foreach ($collection->getAttribute('attributes', []) as $attribute) { + if ( + $attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP + || !$attribute->getAttribute('twoWay') + || $attribute->getAttribute('onDelete') !== Database::RELATION_MUTATE_SET_NULL + ) { + continue; + } + + $value = $document->getAttribute($attribute->getAttribute('key')); + $related = \is_array($value) ? $value : [$value]; + $collectionId = $attribute->getAttribute('relatedCollection'); + $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument( + 'database_' . $database->getSequence(), + $collectionId + )); + if ($relatedCollection->isEmpty()) { + continue; + } + + $sensitive = \array_map( + fn (Document $attribute) => $attribute->getAttribute('key'), + \array_filter( + $relatedCollection->getAttribute('attributes', []), + fn (Document $attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + ) + ); + + foreach ($related as $relation) { + if (!$relation instanceof Document || isset($processed[$collectionId][$relation->getId()])) { + continue; + } + $processed[$collectionId][$relation->getId()] = true; + + // Deleting a child can change a virtual relationship without updating its parent row. + // Read after the delete commits; another relationship may also have deleted this peer. + $current = $authorization->skip(fn () => $dbForDatabases->getDocument( + 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), + $relation->getId() + )); + if ($current->isEmpty()) { + continue; + } + + $this->processDocument( + database: $database, + collection: $relatedCollection, + document: $current, + dbForProject: $dbForProject, + collectionsCache: $collectionsCache, + authorization: $authorization + ); + + // Keep the original delete event intact for the API shutdown hooks. + $event = clone $queueForEvents; + $event->reset() + ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].update') + ->setParam('databaseId', $database->getId()) + ->setParam('collectionId', $collectionId) + ->setParam('tableId', $collectionId) + ->setParam('documentId', $current->getId()) + ->setParam('rowId', $current->getId()) + ->setContext($this->getCollectionsEventsContext(), $relatedCollection) + ->setPayload($response->output($current, $this->getResponseModel()), sensitive: $sensitive); + + $queueForRealtime->from($event)->trigger(); + } + } + } } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php index 9efbd942a39..2dcc363664f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/DocumentsDB/Collections/Documents/Delete.php @@ -68,6 +68,7 @@ public function __construct() ->inject('dbForProject') ->inject('getDatabasesDB') ->inject('queueForEvents') + ->inject('queueForRealtime') ->inject('usage') ->inject('transactionState') ->inject('plan') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php index 12b8ba510c3..cd35555eaa7 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Delete.php @@ -70,6 +70,7 @@ public function __construct() ->inject('dbForProject') ->inject('getDatabasesDB') ->inject('queueForEvents') + ->inject('queueForRealtime') ->inject('usage') ->inject('transactionState') ->inject('plan') diff --git a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php index c6857c2ac6e..cd9df1fc22c 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/VectorsDB/Collections/Documents/Delete.php @@ -68,6 +68,7 @@ public function __construct() ->inject('dbForProject') ->inject('getDatabasesDB') ->inject('queueForEvents') + ->inject('queueForRealtime') ->inject('usage') ->inject('transactionState') ->inject('plan') From 87d37ab90e107854dfe2e1986980dcac7ec1113f Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:45:28 +0530 Subject: [PATCH 384/470] Cover realtime parent updates when deleting one-to-many children --- .../Realtime/RealtimeCustomClientTest.php | 1 + .../Realtime/RealtimeRelationshipBase.php | 145 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/e2e/Services/Realtime/RealtimeRelationshipBase.php diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 6bebbaf52a1..7295d1f49c2 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -22,6 +22,7 @@ final class RealtimeCustomClientTest extends Scope { use FunctionsBase; use RealtimeBase; + use RealtimeRelationshipBase; use ProjectCustom; use SideClient; diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php new file mode 100644 index 00000000000..31d05bfe76c --- /dev/null +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -0,0 +1,145 @@ + 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]; + $tables = $api === 'tablesdb'; + $collections = $tables ? 'tables' : 'collections'; + $documents = $tables ? 'rows' : 'documents'; + $attributes = $tables ? 'columns' : 'attributes'; + $collectionId = $tables ? 'tableId' : 'collectionId'; + $documentId = $tables ? 'rowId' : 'documentId'; + $database = $this->client->call(Client::METHOD_POST, '/' . $api, $headers, [ + 'databaseId' => ID::unique(), + 'name' => 'Relationship events', + ]); + $this->assertSame(201, $database['headers']['status-code']); + $databaseId = $database['body']['$id']; + $base = '/' . $api . '/' . $databaseId . '/' . $collections; + $records = []; + + foreach (['parent', 'child'] as $side) { + $collection = $this->client->call(Client::METHOD_POST, $base, $headers, [ + $collectionId => ID::unique(), + 'name' => $side, + $tables ? 'rowSecurity' : 'documentSecurity' => true, + 'permissions' => [Permission::create(Role::any())], + ]); + $this->assertSame(201, $collection['headers']['status-code']); + $id = $collection['body']['$id']; + $path = $base . '/' . $id; + $attribute = $this->client->call(Client::METHOD_POST, $path . '/' . $attributes . '/string', $headers, [ + 'key' => 'name', + 'size' => 64, + 'required' => false, + ]); + $this->assertSame(202, $attribute['headers']['status-code']); + $this->assertEventually(function () use ($path, $attributes, $headers): void { + $attribute = $this->client->call(Client::METHOD_GET, $path . '/' . $attributes . '/name', $headers); + $this->assertSame('available', $attribute['body']['status']); + }, 30000, 100); + $record = $this->client->call(Client::METHOD_POST, $path . '/' . $documents, $headers, [ + $documentId => ID::unique(), + 'data' => ['name' => $side], + 'permissions' => [Permission::read(Role::any()), Permission::delete(Role::any())], + ]); + $this->assertSame(201, $record['headers']['status-code']); + $records[$side] = [ + 'collectionId' => $id, + 'id' => $record['body']['$id'], + 'path' => $path . '/' . $documents . '/' . $record['body']['$id'], + ]; + } + + $relationship = $this->client->call(Client::METHOD_POST, $base . '/' . $records['parent']['collectionId'] . '/' . $attributes . '/relationship', $headers, [ + $tables ? 'relatedTableId' : 'relatedCollectionId' => $records['child']['collectionId'], + 'type' => $type, + 'twoWay' => true, + 'key' => 'children', + 'twoWayKey' => 'parents', + 'onDelete' => $onDelete, + ]); + $this->assertSame(202, $relationship['headers']['status-code']); + foreach (['parent' => 'children', 'child' => 'parents'] as $side => $key) { + $path = $base . '/' . $records[$side]['collectionId'] . '/' . $attributes . '/' . $key; + $this->assertEventually(function () use ($path, $headers): void { + $attribute = $this->client->call(Client::METHOD_GET, $path, $headers); + $this->assertSame('available', $attribute['body']['status']); + }, 30000, 100); + } + + $many = \in_array($type, ['oneToMany', 'manyToMany']); + $linked = $this->client->call(Client::METHOD_PATCH, $records['parent']['path'], $headers, [ + 'data' => ['children' => $many ? [$records['child']['id']] : $records['child']['id']], + ]); + $this->assertSame(200, $linked['headers']['status-code']); + + return [...$records, 'headers' => $headers, 'databaseId' => $databaseId, 'base' => $base, 'attributes' => $attributes]; + } + + private function subscribeToRelationshipRecord(array $fixture, string $side): WebSocketClient + { + $record = $fixture[$side]; + $socket = $this->getWebsocket([ + 'databases.' . $fixture['databaseId'] . '.collections.' . $record['collectionId'] . '.documents.' . $record['id'], + ], ['origin' => 'http://localhost']); + $connected = \json_decode($socket->receive(), true); + $this->assertSame('connected', $connected['type']); + return $socket; + } + + private function assertRelationshipRemovalEvent(string $type, string $deleted, string $api = 'databases'): void + { + $fixture = $this->createRelationshipRecords($type, $api); + $survivor = $deleted === 'child' ? 'parent' : 'child'; + $key = $survivor === 'parent' ? 'children' : 'parents'; + $socket = $this->subscribeToRelationshipRecord($fixture, $survivor); + try { + // Test for SUCCESS: deleting either side notifies the surviving related record. + $response = $this->client->call(Client::METHOD_DELETE, $fixture[$deleted]['path'], $fixture['headers']); + $this->assertSame(204, $response['headers']['status-code']); + $event = $this->receiveUntilEvent($socket, fn (array $message): bool => ($message['data']['payload']['$id'] ?? null) === $fixture[$survivor]['id']); + $this->assertContains( + 'databases.' . $fixture['databaseId'] . '.collections.' . $fixture[$survivor]['collectionId'] . '.documents.' . $fixture[$survivor]['id'] . '.update', + $event['data']['events'] + ); + $this->assertSame($survivor, $event['data']['payload']['name']); + $this->assertArrayNotHasKey($key, $event['data']['payload']); + $this->assertSame($fixture['databaseId'], $event['data']['payload']['$databaseId']); + $this->assertSame($fixture[$survivor]['collectionId'], $event['data']['payload'][$api === 'tablesdb' ? '$tableId' : '$collectionId']); + + $remaining = $this->client->call(Client::METHOD_GET, $fixture[$survivor]['path'], $fixture['headers']); + $this->assertSame(200, $remaining['headers']['status-code']); + $many = $survivor === 'parent' + ? \in_array($type, ['oneToMany', 'manyToMany']) + : \in_array($type, ['manyToOne', 'manyToMany']); + $this->assertSame($many ? [] : null, $remaining['body'][$key]); + $missing = $this->client->call(Client::METHOD_GET, $fixture[$deleted]['path'], $fixture['headers']); + $this->assertSame(404, $missing['headers']['status-code']); + } finally { + $socket->close(); + } + } + + public function testDeleteOneToManyChildRealtime(): void + { + $this->assertRelationshipRemovalEvent('oneToMany', 'child'); + } +} From cf5f4c83d7bdd2ca60f04bfb9139feb9c5561835 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 385/470] Cover realtime updates after oneToMany parent deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 31d05bfe76c..e1b3edf00a5 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -142,4 +142,9 @@ public function testDeleteOneToManyChildRealtime(): void { $this->assertRelationshipRemovalEvent('oneToMany', 'child'); } + + public function testDeleteOneToManyParentRealtime(): void + { + $this->assertRelationshipRemovalEvent('oneToMany', 'parent'); + } } From e5a949da940fb5a953e52bad0587eec5692a8adc Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 386/470] Cover realtime updates after oneToOne child deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index e1b3edf00a5..0d618ef62e7 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -147,4 +147,9 @@ public function testDeleteOneToManyParentRealtime(): void { $this->assertRelationshipRemovalEvent('oneToMany', 'parent'); } + + public function testDeleteOneToOneChildRealtime(): void + { + $this->assertRelationshipRemovalEvent('oneToOne', 'child'); + } } From 12bcdde78619b7fd2857979480f914462183153a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 387/470] Cover realtime updates after oneToOne parent deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 0d618ef62e7..2c79c14adbf 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -152,4 +152,9 @@ public function testDeleteOneToOneChildRealtime(): void { $this->assertRelationshipRemovalEvent('oneToOne', 'child'); } + + public function testDeleteOneToOneParentRealtime(): void + { + $this->assertRelationshipRemovalEvent('oneToOne', 'parent'); + } } From 89ac20df223dbc3d93a18e8f992de521e6902f4e Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 388/470] Cover realtime updates after manyToOne child deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 2c79c14adbf..cc3f0ea7e0f 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -157,4 +157,9 @@ public function testDeleteOneToOneParentRealtime(): void { $this->assertRelationshipRemovalEvent('oneToOne', 'parent'); } + + public function testDeleteManyToOneChildRealtime(): void + { + $this->assertRelationshipRemovalEvent('manyToOne', 'child'); + } } From 57b82a04391311ac7a4c68f086e26b0d3870e0f7 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 389/470] Cover realtime updates after manyToOne parent deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index cc3f0ea7e0f..89989472526 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -162,4 +162,9 @@ public function testDeleteManyToOneChildRealtime(): void { $this->assertRelationshipRemovalEvent('manyToOne', 'child'); } + + public function testDeleteManyToOneParentRealtime(): void + { + $this->assertRelationshipRemovalEvent('manyToOne', 'parent'); + } } From 54dd9b2a370fe333b00f27032628f856959feeba Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 390/470] Cover realtime updates after manyToMany child deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 89989472526..00299488082 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -167,4 +167,9 @@ public function testDeleteManyToOneParentRealtime(): void { $this->assertRelationshipRemovalEvent('manyToOne', 'parent'); } + + public function testDeleteManyToManyChildRealtime(): void + { + $this->assertRelationshipRemovalEvent('manyToMany', 'child'); + } } From 916bde1566b9bb4b4b10a56b7611c6499c0337dd Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:03 +0530 Subject: [PATCH 391/470] Cover realtime updates after manyToMany parent deletion --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 00299488082..d7d6e703637 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -172,4 +172,9 @@ public function testDeleteManyToManyChildRealtime(): void { $this->assertRelationshipRemovalEvent('manyToMany', 'child'); } + + public function testDeleteManyToManyParentRealtime(): void + { + $this->assertRelationshipRemovalEvent('manyToMany', 'parent'); + } } From dc94bf78c9c8052eb98b3a7ad598b90f9ee26eb6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:46:23 +0530 Subject: [PATCH 392/470] Cover relationship deletion through TablesDB and mirrored row events --- tests/e2e/Services/Realtime/RealtimeRelationshipBase.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index d7d6e703637..6c05472c0bc 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -120,6 +120,10 @@ private function assertRelationshipRemovalEvent(string $type, string $deleted, s 'databases.' . $fixture['databaseId'] . '.collections.' . $fixture[$survivor]['collectionId'] . '.documents.' . $fixture[$survivor]['id'] . '.update', $event['data']['events'] ); + $this->assertContains( + 'databases.' . $fixture['databaseId'] . '.tables.' . $fixture[$survivor]['collectionId'] . '.rows.' . $fixture[$survivor]['id'] . '.update', + $event['data']['events'] + ); $this->assertSame($survivor, $event['data']['payload']['name']); $this->assertArrayNotHasKey($key, $event['data']['payload']); $this->assertSame($fixture['databaseId'], $event['data']['payload']['$databaseId']); @@ -177,4 +181,9 @@ public function testDeleteManyToManyParentRealtime(): void { $this->assertRelationshipRemovalEvent('manyToMany', 'parent'); } + + public function testDeleteRelatedRowRealtime(): void + { + $this->assertRelationshipRemovalEvent('oneToMany', 'child', 'tablesdb'); + } } From a0ebf6c2ed8fdeda6ec02371a713ca926d972e34 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:47:13 +0530 Subject: [PATCH 393/470] Verify client deletion preserves parent realtime permissions --- .../Realtime/RealtimeRelationshipBase.php | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 6c05472c0bc..c7dc9e7e102 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -7,6 +7,7 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use WebSocket\Client as WebSocketClient; +use WebSocket\TimeoutException; trait RealtimeRelationshipBase { @@ -113,7 +114,9 @@ private function assertRelationshipRemovalEvent(string $type, string $deleted, s $socket = $this->subscribeToRelationshipRecord($fixture, $survivor); try { // Test for SUCCESS: deleting either side notifies the surviving related record. - $response = $this->client->call(Client::METHOD_DELETE, $fixture[$deleted]['path'], $fixture['headers']); + $clientHeaders = $fixture['headers']; + unset($clientHeaders['x-appwrite-key']); + $response = $this->client->call(Client::METHOD_DELETE, $fixture[$deleted]['path'], $clientHeaders); $this->assertSame(204, $response['headers']['status-code']); $event = $this->receiveUntilEvent($socket, fn (array $message): bool => ($message['data']['payload']['$id'] ?? null) === $fixture[$survivor]['id']); $this->assertContains( @@ -186,4 +189,36 @@ public function testDeleteRelatedRowRealtime(): void { $this->assertRelationshipRemovalEvent('oneToMany', 'child', 'tablesdb'); } + + private function assertNoRelationshipEvent(WebSocketClient $socket): void + { + try { + $message = $socket->receive(); + $this->fail('Unexpected relationship event: ' . $message); + } catch (TimeoutException) { + $this->addToAssertionCount(1); + } + } + + public function testDeleteRelatedDocumentRealtimePermissions(): void + { + $fixture = $this->createRelationshipRecords('oneToMany'); + $private = $this->client->call(Client::METHOD_PATCH, $fixture['parent']['path'], $fixture['headers'], [ + 'permissions' => [Permission::read(Role::user($this->getUser()['$id']))], + ]); + $this->assertSame(200, $private['headers']['status-code']); + $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); + try { + // Test for FAILURE: a public child never grants access to its private parent event. + $headers = $fixture['headers']; + unset($headers['x-appwrite-key']); + $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); + $this->assertSame(204, $deleted['headers']['status-code']); + $this->assertNoRelationshipEvent($socket); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $this->assertSame([], $parent['body']['children']); + } finally { + $socket->close(); + } + } } From ddad96bc4566779868e44af773aa6948225876f6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:47:46 +0530 Subject: [PATCH 394/470] Cover deduplicated notifications for peers linked by multiple attributes --- .../Realtime/RealtimeRelationshipBase.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index c7dc9e7e102..46730c1cb61 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -221,4 +221,42 @@ public function testDeleteRelatedDocumentRealtimePermissions(): void $socket->close(); } } + + public function testDeleteMultipleRelationshipsRealtime(): void + { + $fixture = $this->createRelationshipRecords('oneToMany'); + $path = $fixture['base'] . '/' . $fixture['parent']['collectionId'] . '/attributes'; + $attribute = $this->client->call(Client::METHOD_POST, $path . '/relationship', $fixture['headers'], [ + 'relatedCollectionId' => $fixture['child']['collectionId'], + 'type' => 'oneToMany', + 'twoWay' => true, + 'key' => 'extras', + 'twoWayKey' => 'owners', + 'onDelete' => 'setNull', + ]); + $this->assertSame(202, $attribute['headers']['status-code']); + $this->assertEventually(function () use ($path, $fixture): void { + $attribute = $this->client->call(Client::METHOD_GET, $path . '/extras', $fixture['headers']); + $this->assertSame('available', $attribute['body']['status']); + }, 30000, 100); + $linked = $this->client->call(Client::METHOD_PATCH, $fixture['parent']['path'], $fixture['headers'], [ + 'data' => ['extras' => [$fixture['child']['id']]], + ]); + $this->assertSame(200, $linked['headers']['status-code']); + $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); + try { + // Test for SUCCESS: a peer linked through two attributes receives one update. + $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $fixture['headers']); + $this->assertSame(204, $deleted['headers']['status-code']); + $event = $this->receiveUntilEvent($socket, fn (array $message): bool => ($message['data']['payload']['$id'] ?? null) === $fixture['parent']['id']); + $this->assertArrayNotHasKey('children', $event['data']['payload']); + $this->assertArrayNotHasKey('extras', $event['data']['payload']); + $this->assertNoRelationshipEvent($socket); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $this->assertSame([], $parent['body']['children']); + $this->assertSame([], $parent['body']['extras']); + } finally { + $socket->close(); + } + } } From 851b5d5f3a5f1eeed9fa5e047597c7a5a38af5cd Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:48:06 +0530 Subject: [PATCH 395/470] Keep restricted relationship deletions silent in realtime --- .../Realtime/RealtimeRelationshipBase.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 46730c1cb61..69be4436ee9 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -259,4 +259,21 @@ public function testDeleteMultipleRelationshipsRealtime(): void $socket->close(); } } + + public function testRestrictedRelationshipDeleteRealtime(): void + { + $fixture = $this->createRelationshipRecords('oneToMany', onDelete: 'restrict'); + $socket = $this->subscribeToRelationshipRecord($fixture, 'child'); + try { + // Test for FAILURE: a rejected delete leaves both the relationship and realtime stream unchanged. + $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['parent']['path'], $fixture['headers']); + $this->assertSame(403, $deleted['headers']['status-code']); + $this->assertSame('document_delete_restricted', $deleted['body']['type']); + $this->assertNoRelationshipEvent($socket); + $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers']); + $this->assertSame($fixture['parent']['id'], $child['body']['parents']['$id']); + } finally { + $socket->close(); + } + } } From b10794607eca2988591c031674f410d5c39ba2a9 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:48:25 +0530 Subject: [PATCH 396/470] Verify denied child deletions cannot publish parent updates --- .../Realtime/RealtimeRelationshipBase.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 69be4436ee9..3ca3d96c2bd 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -276,4 +276,26 @@ public function testRestrictedRelationshipDeleteRealtime(): void $socket->close(); } } + + public function testUnauthorizedRelationshipDeleteRealtime(): void + { + $fixture = $this->createRelationshipRecords('oneToMany'); + $protected = $this->client->call(Client::METHOD_PATCH, $fixture['child']['path'], $fixture['headers'], [ + 'permissions' => [Permission::read(Role::any())], + ]); + $this->assertSame(200, $protected['headers']['status-code']); + $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); + try { + // Test for FAILURE: delete permission is required before publishing any parent update. + $headers = $fixture['headers']; + unset($headers['x-appwrite-key']); + $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); + $this->assertSame(401, $deleted['headers']['status-code']); + $this->assertNoRelationshipEvent($socket); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); + } finally { + $socket->close(); + } + } } From afddd12d2d8bea35220b7a951b10b3158d04585d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:49:09 +0530 Subject: [PATCH 397/470] Prevent realtime updates while relationship deletion is only staged --- .../Realtime/RealtimeRelationshipBase.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index 3ca3d96c2bd..d54eeb1712e 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -298,4 +298,31 @@ public function testUnauthorizedRelationshipDeleteRealtime(): void $socket->close(); } } + + public function testStagedRelationshipDeleteRealtime(): void + { + $fixture = $this->createRelationshipRecords('oneToMany'); + $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', $fixture['headers'], [ + 'ttl' => 60, + ]); + $this->assertSame(201, $transaction['headers']['status-code']); + $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); + try { + // Test for SUCCESS: staging a deletion does not announce a relationship change before commit. + $staged = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $fixture['headers'], [ + 'transactionId' => $transaction['body']['$id'], + ]); + $this->assertSame(204, $staged['headers']['status-code']); + $this->assertNoRelationshipEvent($socket); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); + $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers']); + $this->assertSame(200, $child['headers']['status-code']); + } finally { + $socket->close(); + $this->client->call(Client::METHOD_PATCH, '/databases/transactions/' . $transaction['body']['$id'], $fixture['headers'], [ + 'rollback' => true, + ]); + } + } } From dd77935f7a2d3e2b269f3e496b153dcce6d98673 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:50:59 +0530 Subject: [PATCH 398/470] Replace both collection context aliases for related realtime events --- .../Databases/Http/Databases/Collections/Documents/Delete.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 86ca2877b03..c8cbd146510 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -336,7 +336,8 @@ private function triggerRelationshipUpdates( ->setParam('tableId', $collectionId) ->setParam('documentId', $current->getId()) ->setParam('rowId', $current->getId()) - ->setContext($this->getCollectionsEventsContext(), $relatedCollection) + ->setContext('collection', $relatedCollection) + ->setContext('table', $relatedCollection) ->setPayload($response->output($current, $this->getResponseModel()), sensitive: $sensitive); $queueForRealtime->from($event)->trigger(); From 638ee18a630a91f997f792921266240ac78e4d75 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 15 Sep 2026 00:28:28 -0700 Subject: [PATCH 399/470] Merge pull request #268 from utopia-php/fix/nats-replay-request-after-reconnect fix(nats): replay a request once after a closing -ERR reconnects --- src/Connection.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Connection.php b/src/Connection.php index 698fc754cbc..c493741f3fa 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -206,6 +206,23 @@ public function unsubscribe(Subscription $sub, ?int $maxMessages = null): void } public function request(string $subject, string $data = '', ?float $timeout = null, ?Headers $headers = null): Message + { + try { + return $this->requestOnce($subject, $data, $timeout, $headers); + } catch (ProtocolException $error) { + // Only a stale connection: the server closed it for missed PINGs + // before this PUB was written, so nothing was accepted, and + // handleError() has already reconnected. Other closing errors may + // arrive after the server processed the request, so they surface. + if (!str_contains(strtolower($error->getMessage()), 'stale connection') || $this->status !== self::STATUS_CONNECTED) { + throw $error; + } + + return $this->requestOnce($subject, $data, $timeout, $headers); + } + } + + private function requestOnce(string $subject, string $data, ?float $timeout, ?Headers $headers): Message { $this->ensureConnected(); @@ -227,7 +244,12 @@ public function request(string $subject, string $data = '', ?float $timeout = nu throw new TimeoutException("Request timed out after {$timeout}s"); } - $this->processMessage($remaining); + try { + $this->processMessage($remaining); + } catch (\Throwable $error) { + unset($this->pendingRequests[$token]); + throw $error; + } } $msg = $this->pendingRequests[$token]['message']; From 5e407b99a2ed9c12c9274b68e54b9a1ed58bb86b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 12:59:58 +0530 Subject: [PATCH 400/470] Select relationship fields explicitly for persistence assertions --- .../Realtime/RealtimeRelationshipBase.php | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php index d54eeb1712e..76fd24edc91 100644 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php @@ -6,6 +6,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use WebSocket\Client as WebSocketClient; use WebSocket\TimeoutException; @@ -132,7 +133,9 @@ private function assertRelationshipRemovalEvent(string $type, string $deleted, s $this->assertSame($fixture['databaseId'], $event['data']['payload']['$databaseId']); $this->assertSame($fixture[$survivor]['collectionId'], $event['data']['payload'][$api === 'tablesdb' ? '$tableId' : '$collectionId']); - $remaining = $this->client->call(Client::METHOD_GET, $fixture[$survivor]['path'], $fixture['headers']); + $remaining = $this->client->call(Client::METHOD_GET, $fixture[$survivor]['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', $key . '.*'])->toString()], + ]); $this->assertSame(200, $remaining['headers']['status-code']); $many = $survivor === 'parent' ? \in_array($type, ['oneToMany', 'manyToMany']) @@ -215,7 +218,9 @@ public function testDeleteRelatedDocumentRealtimePermissions(): void $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); $this->assertSame(204, $deleted['headers']['status-code']); $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', 'children.*'])->toString()], + ]); $this->assertSame([], $parent['body']['children']); } finally { $socket->close(); @@ -252,7 +257,9 @@ public function testDeleteMultipleRelationshipsRealtime(): void $this->assertArrayNotHasKey('children', $event['data']['payload']); $this->assertArrayNotHasKey('extras', $event['data']['payload']); $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', 'children.*', 'extras.*'])->toString()], + ]); $this->assertSame([], $parent['body']['children']); $this->assertSame([], $parent['body']['extras']); } finally { @@ -270,7 +277,9 @@ public function testRestrictedRelationshipDeleteRealtime(): void $this->assertSame(403, $deleted['headers']['status-code']); $this->assertSame('document_delete_restricted', $deleted['body']['type']); $this->assertNoRelationshipEvent($socket); - $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers']); + $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', 'parents.*'])->toString()], + ]); $this->assertSame($fixture['parent']['id'], $child['body']['parents']['$id']); } finally { $socket->close(); @@ -292,7 +301,9 @@ public function testUnauthorizedRelationshipDeleteRealtime(): void $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); $this->assertSame(401, $deleted['headers']['status-code']); $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', 'children.*'])->toString()], + ]); $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); } finally { $socket->close(); @@ -314,7 +325,9 @@ public function testStagedRelationshipDeleteRealtime(): void ]); $this->assertSame(204, $staged['headers']['status-code']); $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers']); + $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ + 'queries' => [Query::select(['*', 'children.*'])->toString()], + ]); $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers']); $this->assertSame(200, $child['headers']['status-code']); From 88f0dba9300759f87decb6db5fee62196c141a3f Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 14:32:25 +0530 Subject: [PATCH 401/470] Notify surviving relationship peers for every onDelete mode Restrict and child-side cascade deletes also change surviving two-way peers, so drop the setNull filter; cascaded peers read back empty and are skipped. Re-read peers without loading relationships, and shape the payload through the model filter so output() no longer replaces the audited response payload. --- .../Collections/Documents/Delete.php | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index c8cbd146510..b0e1e69618f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -246,20 +246,24 @@ public function action( ->setPayload($response->output($document, $this->getResponseModel()), sensitive: $relationships); $this->triggerRelationshipUpdates( - $database, - $collection, - $document, - $dbForProject, - $dbForDatabases, - $queueForEvents, - $queueForRealtime, - $response, - $authorization + database: $database, + collection: $collection, + document: $document, + dbForProject: $dbForProject, + dbForDatabases: $dbForDatabases, + queueForEvents: $queueForEvents, + queueForRealtime: $queueForRealtime, + response: $response, + authorization: $authorization ); $response->noContent(); } + /** + * Deleting a document changes two-way relationships on the documents that survive it, + * but the database applies those changes without events, so publish an update for each of them. + */ private function triggerRelationshipUpdates( Document $database, Document $collection, @@ -278,67 +282,71 @@ private function triggerRelationshipUpdates( if ( $attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP || !$attribute->getAttribute('twoWay') - || $attribute->getAttribute('onDelete') !== Database::RELATION_MUTATE_SET_NULL ) { continue; } - $value = $document->getAttribute($attribute->getAttribute('key')); - $related = \is_array($value) ? $value : [$value]; - $collectionId = $attribute->getAttribute('relatedCollection'); + $related = $document->getAttribute($attribute->getAttribute('key')); + $relations = \is_array($related) ? $related : [$related]; + $relatedCollectionId = $attribute->getAttribute('relatedCollection'); $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence(), - $collectionId + $relatedCollectionId )); if ($relatedCollection->isEmpty()) { continue; } $sensitive = \array_map( - fn (Document $attribute) => $attribute->getAttribute('key'), + fn (Document $attr) => $attr->getAttribute('key'), \array_filter( $relatedCollection->getAttribute('attributes', []), - fn (Document $attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn (Document $attr) => $attr->getAttribute('type') === Database::VAR_RELATIONSHIP ) ); - foreach ($related as $relation) { - if (!$relation instanceof Document || isset($processed[$collectionId][$relation->getId()])) { + foreach ($relations as $relation) { + if ( + !$relation instanceof Document + || $relation->isEmpty() + || isset($processed[$relatedCollectionId][$relation->getId()]) + ) { continue; } - $processed[$collectionId][$relation->getId()] = true; + $processed[$relatedCollectionId][$relation->getId()] = true; - // Deleting a child can change a virtual relationship without updating its parent row. - // Read after the delete commits; another relationship may also have deleted this peer. - $current = $authorization->skip(fn () => $dbForDatabases->getDocument( + // Relationship keys are stripped from the payload, so don't load them. + $peer = $authorization->skip(fn () => $dbForDatabases->skipRelationships(fn () => $dbForDatabases->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() - )); - if ($current->isEmpty()) { + ))); + + // Cascaded away by this delete. + if ($peer->isEmpty()) { continue; } $this->processDocument( database: $database, collection: $relatedCollection, - document: $current, + document: $peer, dbForProject: $dbForProject, collectionsCache: $collectionsCache, authorization: $authorization ); - // Keep the original delete event intact for the API shutdown hooks. + // Clone so the delete event stays intact for the shutdown hook. $event = clone $queueForEvents; $event->reset() ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].update') ->setParam('databaseId', $database->getId()) - ->setParam('collectionId', $collectionId) - ->setParam('tableId', $collectionId) - ->setParam('documentId', $current->getId()) - ->setParam('rowId', $current->getId()) - ->setContext('collection', $relatedCollection) - ->setContext('table', $relatedCollection) - ->setPayload($response->output($current, $this->getResponseModel()), sensitive: $sensitive); + ->setParam('collectionId', $relatedCollectionId) + ->setParam('tableId', $relatedCollectionId) + ->setParam('documentId', $peer->getId()) + ->setParam('rowId', $peer->getId()) + ->setContext($this->getCollectionsEventsContext(), $relatedCollection) + // Filter through the model directly; output() would replace the audited response payload. + ->setPayload($response->getModel($this->getResponseModel())->filter($peer)->getArrayCopy(), sensitive: $sensitive); $queueForRealtime->from($event)->trigger(); } From 551d940f5db5e6ac066a2ec008dd3fb75f93c476 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 15 Sep 2026 14:32:25 +0530 Subject: [PATCH 402/470] Move relationship delete realtime tests into RealtimeCustomClientTest Cover setNull, restrict and cascade survivors across cardinalities in one test, permission routing with an owner and a guest socket, and the TablesDB row channel. Drop tests that never reach the new code. --- .../Realtime/RealtimeCustomClientTest.php | 339 ++++++++++++++++- .../Realtime/RealtimeRelationshipBase.php | 341 ------------------ 2 files changed, 338 insertions(+), 342 deletions(-) delete mode 100644 tests/e2e/Services/Realtime/RealtimeRelationshipBase.php diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 7295d1f49c2..ff644ff6117 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -22,7 +22,6 @@ final class RealtimeCustomClientTest extends Scope { use FunctionsBase; use RealtimeBase; - use RealtimeRelationshipBase; use ProjectCustom; use SideClient; @@ -4196,6 +4195,344 @@ public function testRelationshipPayloadHidesRelatedDoc() $client->close(); } + public function testChannelDatabaseRelationshipDelete(): void + { + if (!$this->getSupportForRelationships()) { + $this->expectNotToPerformAssertions(); + return; + } + + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + $headers = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]; + + $database = $this->client->call(Client::METHOD_POST, '/databases', $headers, [ + 'databaseId' => ID::unique(), + 'name' => 'Relationship Delete DB', + ]); + $databaseId = $database['body']['$id']; + + $collections = []; + foreach (['parent', 'child'] as $side) { + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $headers, [ + 'collectionId' => ID::unique(), + 'name' => $side, + 'permissions' => [Permission::create(Role::any())], + 'documentSecurity' => true, + ]); + $collections[$side] = $collection['body']['$id']; + } + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/attributes/string', $headers, [ + 'key' => 'name', + 'size' => 256, + 'required' => false, + ]); + + $this->assertEventually(function () use ($databaseId, $collections, $headers) { + $attribute = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/attributes/name', $headers); + $this->assertEquals('available', $attribute['body']['status']); + }, 30000, 250); + + /** + * Test for SUCCESS + * + * Every successful delete notifies the surviving side, whatever onDelete is. + */ + $cases = [ + ['type' => 'oneToMany', 'onDelete' => 'setNull', 'deleted' => 'child'], + ['type' => 'oneToMany', 'onDelete' => 'restrict', 'deleted' => 'child'], + ['type' => 'oneToMany', 'onDelete' => 'cascade', 'deleted' => 'child'], + ['type' => 'oneToOne', 'onDelete' => 'setNull', 'deleted' => 'parent'], + ['type' => 'manyToOne', 'onDelete' => 'restrict', 'deleted' => 'parent'], + ['type' => 'manyToMany', 'onDelete' => 'cascade', 'deleted' => 'child'], + ]; + + foreach ($cases as $index => $case) { + $key = 'children' . $index; + $twoWayKey = 'parent' . $index; + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/attributes/relationship', $headers, [ + 'relatedCollectionId' => $collections['child'], + 'type' => $case['type'], + 'twoWay' => true, + 'key' => $key, + 'twoWayKey' => $twoWayKey, + 'onDelete' => $case['onDelete'], + ]); + + $this->assertEventually(function () use ($databaseId, $collections, $headers, $key) { + $attribute = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/attributes/' . $key, $headers); + $this->assertEquals('available', $attribute['body']['status']); + }, 30000, 250); + + $permissions = [ + Permission::read(Role::any()), + Permission::delete(Role::any()), + ]; + + $child = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/documents', $headers, [ + 'documentId' => ID::unique(), + 'data' => ['name' => 'child'], + 'permissions' => $permissions, + ]); + $childId = $child['body']['$id']; + + $parent = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/documents', $headers, [ + 'documentId' => ID::unique(), + 'data' => [$key => \in_array($case['type'], ['oneToMany', 'manyToMany']) ? [$childId] : $childId], + 'permissions' => $permissions, + ]); + $this->assertEquals(201, $parent['headers']['status-code']); + + $ids = ['parent' => $parent['body']['$id'], 'child' => $childId]; + $survivor = $case['deleted'] === 'child' ? 'parent' : 'child'; + + $channel = 'databases.' . $databaseId . '.collections.' . $collections[$survivor] . '.documents.' . $ids[$survivor]; + + $client = $this->getWebsocket([$channel], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session, + ]); + $client->receive(); + + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collections[$case['deleted']] . '/documents/' . $ids[$case['deleted']], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + $this->assertEquals(204, $response['headers']['status-code']); + + $this->receiveUntilEvent( + $client, + fn (array $message): bool => \in_array($channel . '.update', $message['data']['events'] ?? [], true), + timeoutMs: 10000 + ); + + $client->close(); + } + } + + public function testChannelDatabaseRelationshipDeletePermissions(): void + { + if (!$this->getSupportForRelationships()) { + $this->expectNotToPerformAssertions(); + return; + } + + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + $headers = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]; + + $database = $this->client->call(Client::METHOD_POST, '/databases', $headers, [ + 'databaseId' => ID::unique(), + 'name' => 'Relationship Delete Permissions DB', + ]); + $databaseId = $database['body']['$id']; + + $collections = []; + foreach (['parent', 'child'] as $side) { + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', $headers, [ + 'collectionId' => ID::unique(), + 'name' => $side, + 'permissions' => [Permission::create(Role::any())], + 'documentSecurity' => true, + ]); + $collections[$side] = $collection['body']['$id']; + } + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/attributes/string', $headers, [ + 'key' => 'name', + 'size' => 256, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/attributes/relationship', $headers, [ + 'relatedCollectionId' => $collections['child'], + 'type' => 'oneToMany', + 'twoWay' => true, + 'key' => 'children', + 'twoWayKey' => 'parent', + 'onDelete' => 'setNull', + ]); + + $this->assertEventually(function () use ($databaseId, $collections, $headers) { + $name = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/attributes/name', $headers); + $this->assertEquals('available', $name['body']['status']); + + $children = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/attributes/children', $headers); + $this->assertEquals('available', $children['body']['status']); + }, 30000, 250); + + $child = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/documents', $headers, [ + 'documentId' => ID::unique(), + 'data' => ['name' => 'child'], + 'permissions' => [ + Permission::read(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $childId = $child['body']['$id']; + + $parent = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collections['parent'] . '/documents', $headers, [ + 'documentId' => ID::unique(), + 'data' => ['children' => [$childId]], + 'permissions' => [Permission::read(Role::user($user['$id']))], + ]); + $parentId = $parent['body']['$id']; + + $channel = 'databases.' . $databaseId . '.collections.' . $collections['parent'] . '.documents.' . $parentId; + + $owner = $this->getWebsocket([$channel], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session, + ]); + $owner->receive(); + + $guest = $this->getWebsocket([$channel], [ + 'origin' => 'http://localhost', + ]); + $guest->receive(); + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collections['child'] . '/documents/' . $childId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + $this->assertEquals(204, $response['headers']['status-code']); + + $this->receiveUntilEvent( + $owner, + fn (array $message): bool => \in_array($channel . '.update', $message['data']['events'] ?? [], true), + timeoutMs: 10000 + ); + + /** + * Test for FAILURE + */ + try { + $guest->receive(); + $this->fail('Guest should not receive an update for a parent it cannot read'); + } catch (TimeoutException) { + $this->addToAssertionCount(1); + } + + $owner->close(); + $guest->close(); + } + + public function testChannelTablesDBRelationshipDelete(): void + { + if (!$this->getSupportForRelationships()) { + $this->expectNotToPerformAssertions(); + return; + } + + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + $headers = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]; + + $database = $this->client->call(Client::METHOD_POST, '/tablesdb', $headers, [ + 'databaseId' => ID::unique(), + 'name' => 'Relationship Delete TablesDB', + ]); + $databaseId = $database['body']['$id']; + + $tables = []; + foreach (['parent', 'child'] as $side) { + $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', $headers, [ + 'tableId' => ID::unique(), + 'name' => $side, + 'permissions' => [Permission::create(Role::any())], + 'rowSecurity' => true, + ]); + $tables[$side] = $table['body']['$id']; + } + + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tables['child'] . '/columns/string', $headers, [ + 'key' => 'name', + 'size' => 256, + 'required' => false, + ]); + + $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tables['parent'] . '/columns/relationship', $headers, [ + 'relatedTableId' => $tables['child'], + 'type' => 'oneToMany', + 'twoWay' => true, + 'key' => 'children', + 'twoWayKey' => 'parent', + 'onDelete' => 'setNull', + ]); + + $this->assertEventually(function () use ($databaseId, $tables, $headers) { + $name = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tables['child'] . '/columns/name', $headers); + $this->assertEquals('available', $name['body']['status']); + + $children = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tables['parent'] . '/columns/children', $headers); + $this->assertEquals('available', $children['body']['status']); + }, 30000, 250); + + $permissions = [ + Permission::read(Role::any()), + Permission::delete(Role::any()), + ]; + + $child = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tables['child'] . '/rows', $headers, [ + 'rowId' => ID::unique(), + 'data' => ['name' => 'child'], + 'permissions' => $permissions, + ]); + $childId = $child['body']['$id']; + + $parent = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tables['parent'] . '/rows', $headers, [ + 'rowId' => ID::unique(), + 'data' => ['children' => [$childId]], + 'permissions' => $permissions, + ]); + $parentId = $parent['body']['$id']; + + $channel = 'databases.' . $databaseId . '.tables.' . $tables['parent'] . '.rows.' . $parentId; + + $client = $this->getWebsocket([$channel], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_' . $projectId . '=' . $session, + ]); + $client->receive(); + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tables['child'] . '/rows/' . $childId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders())); + $this->assertEquals(204, $response['headers']['status-code']); + + $this->receiveUntilEvent( + $client, + fn (array $message): bool => \in_array($channel . '.update', $message['data']['events'] ?? [], true), + timeoutMs: 10000 + ); + + $client->close(); + } + /** * Simulate concurrent realtime traffic using Swoole coroutines. * Opens multiple websocket clients concurrently, then performs create/update/delete ops. diff --git a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php b/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php deleted file mode 100644 index 76fd24edc91..00000000000 --- a/tests/e2e/Services/Realtime/RealtimeRelationshipBase.php +++ /dev/null @@ -1,341 +0,0 @@ - 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]; - $tables = $api === 'tablesdb'; - $collections = $tables ? 'tables' : 'collections'; - $documents = $tables ? 'rows' : 'documents'; - $attributes = $tables ? 'columns' : 'attributes'; - $collectionId = $tables ? 'tableId' : 'collectionId'; - $documentId = $tables ? 'rowId' : 'documentId'; - $database = $this->client->call(Client::METHOD_POST, '/' . $api, $headers, [ - 'databaseId' => ID::unique(), - 'name' => 'Relationship events', - ]); - $this->assertSame(201, $database['headers']['status-code']); - $databaseId = $database['body']['$id']; - $base = '/' . $api . '/' . $databaseId . '/' . $collections; - $records = []; - - foreach (['parent', 'child'] as $side) { - $collection = $this->client->call(Client::METHOD_POST, $base, $headers, [ - $collectionId => ID::unique(), - 'name' => $side, - $tables ? 'rowSecurity' : 'documentSecurity' => true, - 'permissions' => [Permission::create(Role::any())], - ]); - $this->assertSame(201, $collection['headers']['status-code']); - $id = $collection['body']['$id']; - $path = $base . '/' . $id; - $attribute = $this->client->call(Client::METHOD_POST, $path . '/' . $attributes . '/string', $headers, [ - 'key' => 'name', - 'size' => 64, - 'required' => false, - ]); - $this->assertSame(202, $attribute['headers']['status-code']); - $this->assertEventually(function () use ($path, $attributes, $headers): void { - $attribute = $this->client->call(Client::METHOD_GET, $path . '/' . $attributes . '/name', $headers); - $this->assertSame('available', $attribute['body']['status']); - }, 30000, 100); - $record = $this->client->call(Client::METHOD_POST, $path . '/' . $documents, $headers, [ - $documentId => ID::unique(), - 'data' => ['name' => $side], - 'permissions' => [Permission::read(Role::any()), Permission::delete(Role::any())], - ]); - $this->assertSame(201, $record['headers']['status-code']); - $records[$side] = [ - 'collectionId' => $id, - 'id' => $record['body']['$id'], - 'path' => $path . '/' . $documents . '/' . $record['body']['$id'], - ]; - } - - $relationship = $this->client->call(Client::METHOD_POST, $base . '/' . $records['parent']['collectionId'] . '/' . $attributes . '/relationship', $headers, [ - $tables ? 'relatedTableId' : 'relatedCollectionId' => $records['child']['collectionId'], - 'type' => $type, - 'twoWay' => true, - 'key' => 'children', - 'twoWayKey' => 'parents', - 'onDelete' => $onDelete, - ]); - $this->assertSame(202, $relationship['headers']['status-code']); - foreach (['parent' => 'children', 'child' => 'parents'] as $side => $key) { - $path = $base . '/' . $records[$side]['collectionId'] . '/' . $attributes . '/' . $key; - $this->assertEventually(function () use ($path, $headers): void { - $attribute = $this->client->call(Client::METHOD_GET, $path, $headers); - $this->assertSame('available', $attribute['body']['status']); - }, 30000, 100); - } - - $many = \in_array($type, ['oneToMany', 'manyToMany']); - $linked = $this->client->call(Client::METHOD_PATCH, $records['parent']['path'], $headers, [ - 'data' => ['children' => $many ? [$records['child']['id']] : $records['child']['id']], - ]); - $this->assertSame(200, $linked['headers']['status-code']); - - return [...$records, 'headers' => $headers, 'databaseId' => $databaseId, 'base' => $base, 'attributes' => $attributes]; - } - - private function subscribeToRelationshipRecord(array $fixture, string $side): WebSocketClient - { - $record = $fixture[$side]; - $socket = $this->getWebsocket([ - 'databases.' . $fixture['databaseId'] . '.collections.' . $record['collectionId'] . '.documents.' . $record['id'], - ], ['origin' => 'http://localhost']); - $connected = \json_decode($socket->receive(), true); - $this->assertSame('connected', $connected['type']); - return $socket; - } - - private function assertRelationshipRemovalEvent(string $type, string $deleted, string $api = 'databases'): void - { - $fixture = $this->createRelationshipRecords($type, $api); - $survivor = $deleted === 'child' ? 'parent' : 'child'; - $key = $survivor === 'parent' ? 'children' : 'parents'; - $socket = $this->subscribeToRelationshipRecord($fixture, $survivor); - try { - // Test for SUCCESS: deleting either side notifies the surviving related record. - $clientHeaders = $fixture['headers']; - unset($clientHeaders['x-appwrite-key']); - $response = $this->client->call(Client::METHOD_DELETE, $fixture[$deleted]['path'], $clientHeaders); - $this->assertSame(204, $response['headers']['status-code']); - $event = $this->receiveUntilEvent($socket, fn (array $message): bool => ($message['data']['payload']['$id'] ?? null) === $fixture[$survivor]['id']); - $this->assertContains( - 'databases.' . $fixture['databaseId'] . '.collections.' . $fixture[$survivor]['collectionId'] . '.documents.' . $fixture[$survivor]['id'] . '.update', - $event['data']['events'] - ); - $this->assertContains( - 'databases.' . $fixture['databaseId'] . '.tables.' . $fixture[$survivor]['collectionId'] . '.rows.' . $fixture[$survivor]['id'] . '.update', - $event['data']['events'] - ); - $this->assertSame($survivor, $event['data']['payload']['name']); - $this->assertArrayNotHasKey($key, $event['data']['payload']); - $this->assertSame($fixture['databaseId'], $event['data']['payload']['$databaseId']); - $this->assertSame($fixture[$survivor]['collectionId'], $event['data']['payload'][$api === 'tablesdb' ? '$tableId' : '$collectionId']); - - $remaining = $this->client->call(Client::METHOD_GET, $fixture[$survivor]['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', $key . '.*'])->toString()], - ]); - $this->assertSame(200, $remaining['headers']['status-code']); - $many = $survivor === 'parent' - ? \in_array($type, ['oneToMany', 'manyToMany']) - : \in_array($type, ['manyToOne', 'manyToMany']); - $this->assertSame($many ? [] : null, $remaining['body'][$key]); - $missing = $this->client->call(Client::METHOD_GET, $fixture[$deleted]['path'], $fixture['headers']); - $this->assertSame(404, $missing['headers']['status-code']); - } finally { - $socket->close(); - } - } - - public function testDeleteOneToManyChildRealtime(): void - { - $this->assertRelationshipRemovalEvent('oneToMany', 'child'); - } - - public function testDeleteOneToManyParentRealtime(): void - { - $this->assertRelationshipRemovalEvent('oneToMany', 'parent'); - } - - public function testDeleteOneToOneChildRealtime(): void - { - $this->assertRelationshipRemovalEvent('oneToOne', 'child'); - } - - public function testDeleteOneToOneParentRealtime(): void - { - $this->assertRelationshipRemovalEvent('oneToOne', 'parent'); - } - - public function testDeleteManyToOneChildRealtime(): void - { - $this->assertRelationshipRemovalEvent('manyToOne', 'child'); - } - - public function testDeleteManyToOneParentRealtime(): void - { - $this->assertRelationshipRemovalEvent('manyToOne', 'parent'); - } - - public function testDeleteManyToManyChildRealtime(): void - { - $this->assertRelationshipRemovalEvent('manyToMany', 'child'); - } - - public function testDeleteManyToManyParentRealtime(): void - { - $this->assertRelationshipRemovalEvent('manyToMany', 'parent'); - } - - public function testDeleteRelatedRowRealtime(): void - { - $this->assertRelationshipRemovalEvent('oneToMany', 'child', 'tablesdb'); - } - - private function assertNoRelationshipEvent(WebSocketClient $socket): void - { - try { - $message = $socket->receive(); - $this->fail('Unexpected relationship event: ' . $message); - } catch (TimeoutException) { - $this->addToAssertionCount(1); - } - } - - public function testDeleteRelatedDocumentRealtimePermissions(): void - { - $fixture = $this->createRelationshipRecords('oneToMany'); - $private = $this->client->call(Client::METHOD_PATCH, $fixture['parent']['path'], $fixture['headers'], [ - 'permissions' => [Permission::read(Role::user($this->getUser()['$id']))], - ]); - $this->assertSame(200, $private['headers']['status-code']); - $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); - try { - // Test for FAILURE: a public child never grants access to its private parent event. - $headers = $fixture['headers']; - unset($headers['x-appwrite-key']); - $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); - $this->assertSame(204, $deleted['headers']['status-code']); - $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', 'children.*'])->toString()], - ]); - $this->assertSame([], $parent['body']['children']); - } finally { - $socket->close(); - } - } - - public function testDeleteMultipleRelationshipsRealtime(): void - { - $fixture = $this->createRelationshipRecords('oneToMany'); - $path = $fixture['base'] . '/' . $fixture['parent']['collectionId'] . '/attributes'; - $attribute = $this->client->call(Client::METHOD_POST, $path . '/relationship', $fixture['headers'], [ - 'relatedCollectionId' => $fixture['child']['collectionId'], - 'type' => 'oneToMany', - 'twoWay' => true, - 'key' => 'extras', - 'twoWayKey' => 'owners', - 'onDelete' => 'setNull', - ]); - $this->assertSame(202, $attribute['headers']['status-code']); - $this->assertEventually(function () use ($path, $fixture): void { - $attribute = $this->client->call(Client::METHOD_GET, $path . '/extras', $fixture['headers']); - $this->assertSame('available', $attribute['body']['status']); - }, 30000, 100); - $linked = $this->client->call(Client::METHOD_PATCH, $fixture['parent']['path'], $fixture['headers'], [ - 'data' => ['extras' => [$fixture['child']['id']]], - ]); - $this->assertSame(200, $linked['headers']['status-code']); - $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); - try { - // Test for SUCCESS: a peer linked through two attributes receives one update. - $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $fixture['headers']); - $this->assertSame(204, $deleted['headers']['status-code']); - $event = $this->receiveUntilEvent($socket, fn (array $message): bool => ($message['data']['payload']['$id'] ?? null) === $fixture['parent']['id']); - $this->assertArrayNotHasKey('children', $event['data']['payload']); - $this->assertArrayNotHasKey('extras', $event['data']['payload']); - $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', 'children.*', 'extras.*'])->toString()], - ]); - $this->assertSame([], $parent['body']['children']); - $this->assertSame([], $parent['body']['extras']); - } finally { - $socket->close(); - } - } - - public function testRestrictedRelationshipDeleteRealtime(): void - { - $fixture = $this->createRelationshipRecords('oneToMany', onDelete: 'restrict'); - $socket = $this->subscribeToRelationshipRecord($fixture, 'child'); - try { - // Test for FAILURE: a rejected delete leaves both the relationship and realtime stream unchanged. - $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['parent']['path'], $fixture['headers']); - $this->assertSame(403, $deleted['headers']['status-code']); - $this->assertSame('document_delete_restricted', $deleted['body']['type']); - $this->assertNoRelationshipEvent($socket); - $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', 'parents.*'])->toString()], - ]); - $this->assertSame($fixture['parent']['id'], $child['body']['parents']['$id']); - } finally { - $socket->close(); - } - } - - public function testUnauthorizedRelationshipDeleteRealtime(): void - { - $fixture = $this->createRelationshipRecords('oneToMany'); - $protected = $this->client->call(Client::METHOD_PATCH, $fixture['child']['path'], $fixture['headers'], [ - 'permissions' => [Permission::read(Role::any())], - ]); - $this->assertSame(200, $protected['headers']['status-code']); - $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); - try { - // Test for FAILURE: delete permission is required before publishing any parent update. - $headers = $fixture['headers']; - unset($headers['x-appwrite-key']); - $deleted = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $headers); - $this->assertSame(401, $deleted['headers']['status-code']); - $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', 'children.*'])->toString()], - ]); - $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); - } finally { - $socket->close(); - } - } - - public function testStagedRelationshipDeleteRealtime(): void - { - $fixture = $this->createRelationshipRecords('oneToMany'); - $transaction = $this->client->call(Client::METHOD_POST, '/databases/transactions', $fixture['headers'], [ - 'ttl' => 60, - ]); - $this->assertSame(201, $transaction['headers']['status-code']); - $socket = $this->subscribeToRelationshipRecord($fixture, 'parent'); - try { - // Test for SUCCESS: staging a deletion does not announce a relationship change before commit. - $staged = $this->client->call(Client::METHOD_DELETE, $fixture['child']['path'], $fixture['headers'], [ - 'transactionId' => $transaction['body']['$id'], - ]); - $this->assertSame(204, $staged['headers']['status-code']); - $this->assertNoRelationshipEvent($socket); - $parent = $this->client->call(Client::METHOD_GET, $fixture['parent']['path'], $fixture['headers'], [ - 'queries' => [Query::select(['*', 'children.*'])->toString()], - ]); - $this->assertSame($fixture['child']['id'], $parent['body']['children'][0]['$id']); - $child = $this->client->call(Client::METHOD_GET, $fixture['child']['path'], $fixture['headers']); - $this->assertSame(200, $child['headers']['status-code']); - } finally { - $socket->close(); - $this->client->call(Client::METHOD_PATCH, '/databases/transactions/' . $transaction['body']['$id'], $fixture['headers'], [ - 'rollback' => true, - ]); - } - } -} From 016f48bd25f7bf7a06e5e12a9c768036945d5459 Mon Sep 17 00:00:00 2001 From: "Luke B. Silver" <22452787+loks0n@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:07:22 +0100 Subject: [PATCH 403/470] Merge pull request #315 from utopia-php/codex/nats-requests Pipeline NATS requests with individual confirmations --- README.md | 31 ++ src/Connection.php | 235 +++++++++----- src/Internal/CallbackException.php | 14 + src/JetStream/JetStream.php | 18 ++ src/Protocol/Parser.php | 6 + src/Request.php | 20 ++ tests/E2E/JetStreamExtraTest.php | 28 ++ tests/E2E/RequestsTest.php | 46 +++ tests/Unit/RequestsTest.php | 448 +++++++++++++++++++++++++++ tests/Unit/Support/FakeTransport.php | 4 + 10 files changed, 766 insertions(+), 84 deletions(-) create mode 100644 src/Internal/CallbackException.php create mode 100644 src/Request.php create mode 100644 tests/E2E/RequestsTest.php create mode 100644 tests/Unit/RequestsTest.php diff --git a/README.md b/README.md index 09ebee726dc..23015f49d91 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,37 @@ $conn = Connection::connect(new ConnectionOptions( NATS_URL=nats://host:4222 ./vendor/bin/phpunit --testsuite integration ``` +## Batched requests + +`Connection::requestBatch()` sends independent requests together and delivers each outcome to `reply` as it arrives. The callback receives the original input index and either a `Message` or a `Throwable`. One missing reply does not delay successful replies or discard their outcomes. + +```php +$connection->requestBatch( + requests: [ + new Request(subject: 'service.first', data: 'one'), + new Request(subject: 'service.second', data: 'two', headers: $headers), + ], + reply: function (int $index, Message|Throwable $result): void { + // Handle this request's reply or failure. + }, + timeout: 5.0, +); +``` + +`Request` has read-only typed fields; both `request()` and `requestBatch()` use its subject validation and the same request lifecycle. The method returns `void`; collect results in the callback if you need an array. Completion order can differ from input order. The timeout is one response deadline for the whole group, starting after the write, rather than a separate wait per reply. Callback execution counts toward that deadline. Empty input performs no I/O. Invalid input throws before any request is published; request headers and payload limits follow `request()`. + +If the callback throws, collection stops immediately, pending state is cleaned up, and the exception propagates. Requests already sent are not cancelled or replayed. Later replies can still arrive. Use one owner for reading the connection; nested reads from the callback throw `LogicException`. + +This differs from `requestMany()`, which sends one request and gathers several responses. Ambiguous writes are not replayed on reconnect. Use `JetStream::ackBatch()` to confirm a selected list of `JetStreamMessage` instances. JetStream constructs each acknowledgement; its callback receives the original index and `null` on server confirmation or a `Throwable` on failure. The consumer's configured acknowledgement policy still applies. Use `AckPolicy::Explicit` to leave messages outside the list unacknowledged. With `AckPolicy::All`, acknowledging a later message also acknowledges earlier messages, including those outside the list. + +```php +$jetStream->ackBatch($messages, function (int $index, ?Throwable $error): void { + // Null means this message's acknowledgement was confirmed. +}); +``` + +Single `request()` calls retain one retry for an explicit stale-connection rejection. Batch requests do not retry. Neither path replays an ambiguous write. + ## License Apache-2.0 diff --git a/src/Connection.php b/src/Connection.php index c493741f3fa..ceca11dc9b7 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -17,6 +17,7 @@ use Utopia\NATS\Exception\PermissionException; use Utopia\NATS\Exception\ProtocolException; use Utopia\NATS\Exception\TimeoutException; +use Utopia\NATS\Internal\CallbackException; use Utopia\NATS\Protocol\Parser; use Utopia\NATS\Protocol\ServerOp; use Utopia\NATS\Protocol\Writer; @@ -98,8 +99,7 @@ final class Connection // Mux inbox for request-reply private ?Subscription $inboxSub = null; private string $inboxPrefix = ''; - /** @var array */ - private array $pendingRequests = []; + private bool $collecting = false; // Reconnection /** @var list */ @@ -142,6 +142,11 @@ public function publish(string $subject, string $data = '', ?string $replyTo = n { $this->ensureConnected(); + $this->send($this->encode($subject, $data, $replyTo, $headers)); + } + + private function encode(string $subject, string $data, ?string $replyTo, ?Headers $headers): string + { $hasHeaders = $headers instanceof \Utopia\NATS\Headers && $headers->all() !== []; if ($hasHeaders && isset($this->serverInfo) && !$this->serverInfo->headersSupported) { @@ -157,13 +162,10 @@ public function publish(string $subject, string $data = '', ?string $replyTo = n ); } - if ($hasHeaders) { - $cmd = $this->writer->hpub($subject, $headerWire, $data, $replyTo); - } else { - $cmd = $this->writer->pub($subject, $data, $replyTo); - } + return $hasHeaders + ? $this->writer->hpub($subject, $headerWire, $data, $replyTo) + : $this->writer->pub($subject, $data, $replyTo); - $this->send($cmd); } public function subscribe(string $subject, ?\Closure $callback = null, ?string $queue = null): Subscription @@ -171,14 +173,15 @@ public function subscribe(string $subject, ?\Closure $callback = null, ?string $ $this->ensureConnected(); $sid = (string) $this->nextSid++; + $slow = $this->options->onSlowConsumer; $sub = new Subscription( $sid, $subject, $queue, - $callback, + $callback instanceof \Closure ? fn(Message $message) => $this->notify($callback, $message) : null, $this->options->subPendingMsgsLimit, $this->options->subPendingBytesLimit, - $this->options->onSlowConsumer, + $slow instanceof \Closure ? fn(Subscription $subscription) => $this->notify($slow, $subscription) : null, ); $sub->setConnection($this); @@ -207,64 +210,119 @@ public function unsubscribe(Subscription $sub, ?int $maxMessages = null): void public function request(string $subject, string $data = '', ?float $timeout = null, ?Headers $headers = null): Message { - try { - return $this->requestOnce($subject, $data, $timeout, $headers); - } catch (ProtocolException $error) { - // Only a stale connection: the server closed it for missed PINGs - // before this PUB was written, so nothing was accepted, and - // handleError() has already reconnected. Other closing errors may - // arrive after the server processed the request, so they surface. - if (!str_contains(strtolower($error->getMessage()), 'stale connection') || $this->status !== self::STATUS_CONNECTED) { - throw $error; + $request = new Request($subject, $data, $headers); + for ($attempt = 0; ; $attempt++) { + $result = null; + $this->requestBatch([$request], static function (int $index, Message|\Throwable $response) use (&$result): void { + $result = $response; + }, $timeout); + // Only the single-request API retries a server's explicit stale rejection. + // Ambiguous writes and other errors are never replayed. + if ($attempt === 0 && $result instanceof ProtocolException + && str_contains(strtolower($result->getMessage()), 'stale connection') + && $this->options->allowReconnect && $this->status === self::STATUS_DISCONNECTED) { + $this->attemptReconnect(); + continue; } - - return $this->requestOnce($subject, $data, $timeout, $headers); + if ($result instanceof \Throwable) { + throw $result; + } + return $result ?? throw new \LogicException('Request completed without an outcome'); } } - private function requestOnce(string $subject, string $data, ?float $timeout, ?Headers $headers): Message + /** + * Send independent requests together. Deliver indexed outcomes as replies arrive. + * One response deadline starts after writing; ambiguous writes are never replayed. + * Callback exceptions abort collection, not already-sent remote work. + * + * @param list $requests + * @param \Closure(int, Message|\Throwable): void $reply + */ + public function requestBatch(array $requests, \Closure $reply, ?float $timeout = null): void { - $this->ensureConnected(); - + $this->assertReadable(); $timeout ??= $this->options->requestTimeout; - $this->ensureInboxSub(); - - $token = Inbox::generateId(); - $replyTo = $this->inboxPrefix . '.' . $token; - $this->pendingRequests[$token] = ['message' => null, 'resolved' => false]; - - $this->publish($subject, $data, $replyTo, $headers); - - $deadline = microtime(true) + $timeout; - - while (!$this->pendingRequests[$token]['resolved']) { - $remaining = $deadline - microtime(true); - if ($remaining <= 0) { - unset($this->pendingRequests[$token]); - throw new TimeoutException("Request timed out after {$timeout}s"); + if (!is_finite($timeout) || $timeout <= 0 || !array_is_list($requests)) { + throw new \InvalidArgumentException('Requests must be a list with a finite positive timeout'); + } + foreach ($requests as $request) { + if (!$request instanceof Request) { + throw new \InvalidArgumentException('Expected Request objects'); } - + } + if ($requests === []) { + return; + } + $pending = []; + $publishing = $written = false; + $this->collecting = true; + try { try { - $this->processMessage($remaining); - } catch (\Throwable $error) { - unset($this->pendingRequests[$token]); - throw $error; + $this->ensureConnected(); + $this->ensureInboxSub(); + $wire = ''; + foreach ($requests as $index => $request) { + $token = Inbox::generateId(); + $pending[$token] = $index; + $wire .= $this->encode($request->subject, $request->data, $this->inboxPrefix . '.' . $token, $request->headers); + } + $publishing = true; + $this->transport->write($wire); + $written = true; + $deadline = hrtime(true) / 1e9 + $timeout; + while ($pending !== []) { + $remaining = $deadline - hrtime(true) / 1e9; + if ($remaining <= 0) { + throw new TimeoutException("Requests timed out after {$timeout}s"); + } + $this->checkPings(); + $message = $this->readMessage($remaining, reconnect: false); + $token = $message instanceof Message ? $this->extractInboxToken($message->subject) : null; + if ($token !== null && isset($pending[$token])) { + $index = $pending[$token]; + unset($pending[$token]); + $response = $message->headers?->getStatus() === '503' + ? new NatsException('No responders for request') : $message; + $this->notify($reply, $index, $response); + } + } + } catch (NatsException $error) { + if ($written && $error instanceof TimeoutException) { + $error = new TimeoutException("Request timed out after {$timeout}s", previous: $error); + } + if ($error instanceof ConnectionException && (!$written || !$error instanceof TimeoutException)) { + $this->recycleDeadConnection(false); + } + if (!$publishing) { + throw $error; + } + foreach ($pending as $index) { + $this->notify($reply, $index, $error); + } } + } catch (CallbackException $error) { + throw $error->cause; + } finally { + $this->collecting = false; } + } - $msg = $this->pendingRequests[$token]['message']; - unset($this->pendingRequests[$token]); - - if ($msg === null) { - throw new TimeoutException("Request timed out after {$timeout}s"); + private function assertReadable(): void + { + if ($this->collecting) { + throw new \LogicException('Cannot read the connection while collecting requests'); } + } - // Check for no responders (status 503) - if ($msg->headers !== null && $msg->headers->getStatus() === '503') { - throw new NatsException('No responders for request'); + /** Preserve exception provenance across protocol dispatch and application callbacks. */ + private function notify(\Closure $callback, mixed ...$arguments): void + { + try { + $callback(...$arguments); + } catch (\Throwable $error) { + throw $this->collecting && !$error instanceof CallbackException ? new CallbackException($error) : $error; } - - return $msg; } /** @@ -280,6 +338,7 @@ private function requestOnce(string $subject, string $data, ?float $timeout, ?He */ public function requestMany(string $subject, string $data = '', array $opts = []): array { + $this->assertReadable(); $this->ensureConnected(); $max = $opts['max'] ?? null; @@ -344,6 +403,7 @@ public function newInbox(): string */ public function tick(): void { + $this->assertReadable(); $this->checkPings(); $this->collectPongs(); } @@ -375,6 +435,7 @@ private function collectPongs(): void */ public function processMessage(?float $timeout = null): ?Message { + $this->assertReadable(); $this->checkPings(); return $this->readMessage($timeout); @@ -384,14 +445,17 @@ public function processMessage(?float $timeout = null): ?Message * The read half of {@see self::processMessage()}, without the keepalive * check, so maintenance can drain the socket without writing to it. */ - private function readMessage(?float $timeout = null): ?Message + private function readMessage(?float $timeout = null, bool $reconnect = true): ?Message { try { [$op, $data] = $this->parser->next($timeout); - } catch (TimeoutException) { + } catch (TimeoutException $error) { + if (!$reconnect) { + throw $error; + } return null; } catch (ConnectionException $e) { - if ($this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { + if ($reconnect && $this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { $this->attemptReconnect(); return null; } @@ -431,6 +495,7 @@ public function jetStream(?string $domain = null, ?string $apiPrefix = null): Je public function flush(?float $timeout = null): void { + $this->assertReadable(); $this->ensureConnected(); $timeout ??= $this->options->connectTimeout; @@ -454,6 +519,7 @@ public function flush(?float $timeout = null): void public function drain(?float $timeout = null): void { + $this->assertReadable(); if ($this->status !== self::STATUS_CONNECTED) { return; } @@ -507,14 +573,13 @@ public function close(): void $sub->setInactive(); } $this->subscriptions = []; - $this->pendingRequests = []; if (isset($this->transport)) { $this->transport->close(); } if ($previousStatus === self::STATUS_CONNECTED && $this->options->onClose instanceof \Closure) { - ($this->options->onClose)(); + $this->notify($this->options->onClose); } } @@ -717,13 +782,9 @@ private function handleMessage(array $data): Message sid: $data['sid'], ); - // Check if this is a reply to a pending request (mux inbox) - if ($this->inboxSub instanceof \Utopia\NATS\Subscription && $data['sid'] === $this->inboxSub->sid) { - $token = $this->extractInboxToken($data['subject']); - if ($token !== null && isset($this->pendingRequests[$token])) { - $this->pendingRequests[$token] = ['message' => $msg, 'resolved' => true]; - return $msg; - } + // Private inbox replies belong to the active request reader, never a backlog. + if ($this->inboxSub instanceof Subscription && $data['sid'] === $this->inboxSub->sid) { + return $msg; } // Dispatch to subscription @@ -757,15 +818,13 @@ private function handleError(mixed $data): never $message = \is_string($data) ? $data : 'Unknown server error'; $error = self::mapServerError($message); - if ($this->options->onError instanceof \Closure) { - ($this->options->onError)(new NatsException($message)); + // Closing errors must make the connection unusable even when onError throws. + if (self::closesConnection($message)) { + $this->recycleDeadConnection(!$this->collecting && self::reconnectsAfter($message)); } - // The server has already closed the socket for these errors, but the - // status still reads "connected", so the next publish() would write into - // a dead socket and return success. Recycle the connection here instead. - if (self::closesConnection($message)) { - $this->recycleDeadConnection(self::reconnectsAfter($message)); + if ($this->options->onError instanceof \Closure) { + $this->notify($this->options->onError, new NatsException($message)); } throw $error; @@ -899,7 +958,7 @@ private function handleInfo(mixed $data): null private function handleLameDuck(): void { if ($this->options->onLameDuck instanceof \Closure) { - ($this->options->onLameDuck)(); + $this->notify($this->options->onLameDuck); } $others = array_values(array_filter( @@ -909,7 +968,7 @@ private function handleLameDuck(): void // Only proactively reconnect when a different server is available; // otherwise ride out the current connection until it is closed. - if ($others !== [] && $this->options->allowReconnect) { + if ($others !== [] && !$this->collecting && $this->options->allowReconnect) { $this->serverPool = [...$others, $this->currentServer]; $this->attemptReconnect(); } @@ -919,7 +978,7 @@ private function handleLameDuck(): void * Emit the keepalive PING when one is due, and act on a spent budget first. * * Only called from paths that also read the socket -- {@see - * self::processMessage()} and {@see self::tick()} -- because a PING is only + * self::processMessage()}, {@see self::requestBatch()} and {@see self::tick()} -- because a PING is only * half a keepalive. It is the read that clears $outstandingPings, so a * caller that sends without reading marches the count to maxPingsOut and * declares a healthy connection stale. See {@see self::ensureConnected()} @@ -927,7 +986,8 @@ private function handleLameDuck(): void */ private function checkPings(): void { - if ($this->status !== self::STATUS_CONNECTED) { + // Drain received operations before sending another probe or judging its reply. + if ($this->status !== self::STATUS_CONNECTED || $this->parser->hasBufferedData()) { return; } @@ -944,7 +1004,10 @@ private function checkPings(): void $this->send($this->writer->ping()); $this->outstandingPings++; $this->lastPingTime = $now; - } catch (ConnectionException) { + } catch (ConnectionException $error) { + if ($this->collecting) { + throw $error; + } if ($this->options->allowReconnect) { $this->attemptReconnect(); } @@ -961,7 +1024,8 @@ private function checkPings(): void */ private function checkStale(): bool { - if ($this->status !== self::STATUS_CONNECTED) { + // An owed PONG may already be buffered, including between request batches. + if ($this->status !== self::STATUS_CONNECTED || $this->parser->hasBufferedData()) { return false; } @@ -969,7 +1033,7 @@ private function checkStale(): bool return false; } - if ($this->options->allowReconnect) { + if (!$this->collecting && $this->options->allowReconnect) { $this->attemptReconnect(); return true; @@ -987,7 +1051,7 @@ private function attemptReconnect(): void $this->status = self::STATUS_RECONNECTING; if ($this->options->onDisconnect instanceof \Closure) { - ($this->options->onDisconnect)(); + $this->notify($this->options->onDisconnect); } if (isset($this->transport)) { @@ -1028,10 +1092,12 @@ private function attemptReconnect(): void } if ($this->options->onReconnect instanceof \Closure) { - ($this->options->onReconnect)(); + $this->notify($this->options->onReconnect); } return; + } catch (CallbackException $error) { + throw $error; } catch (\Throwable) { continue; } @@ -1075,11 +1141,12 @@ private function send(string $data): void try { $this->transport->write($data); } catch (ConnectionException $e) { - if ($this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { + if (!$this->collecting && $this->options->allowReconnect && $this->status !== self::STATUS_CLOSED) { $this->bufferPending($data); $this->attemptReconnect(); return; } + $this->recycleDeadConnection(false); throw $e; } } @@ -1093,7 +1160,7 @@ private function bufferPending(string $data): void { if (!self::reconnectBufferAccepts($this->pendingBufferBytes, \strlen($data), $this->options->reconnectBufSize)) { if ($this->options->onError instanceof \Closure) { - ($this->options->onError)(new NatsException('Reconnect buffer full; dropping pending message')); + $this->notify($this->options->onError, new NatsException('Reconnect buffer full; dropping pending message')); } return; } diff --git a/src/Internal/CallbackException.php b/src/Internal/CallbackException.php new file mode 100644 index 00000000000..57b396c6e34 --- /dev/null +++ b/src/Internal/CallbackException.php @@ -0,0 +1,14 @@ +getMessage(), previous: $cause); + } +} diff --git a/src/JetStream/JetStream.php b/src/JetStream/JetStream.php index 1a5719beda0..2dd2b19d7e6 100644 --- a/src/JetStream/JetStream.php +++ b/src/JetStream/JetStream.php @@ -13,6 +13,7 @@ use Utopia\NATS\KeyValue\KeyValue; use Utopia\NATS\KeyValue\KeyValueConfig; use Utopia\NATS\Message; +use Utopia\NATS\Request; final class JetStream { @@ -39,6 +40,23 @@ public function __construct( } } + /** + * Confirm acknowledgements for the selected messages using the consumer policy. + * AckPolicy::Explicit is required to leave unselected messages unacknowledged. + * With AckPolicy::All, acknowledging a later message also acknowledges earlier ones. + * @param list $messages + * @param \Closure(int, ?\Throwable): void $confirmed Null means server-confirmed. + */ + public function ackBatch(array $messages, \Closure $confirmed, ?float $timeout = null): void + { + $requests = array_map(static fn(JetStreamMessage $message): Request => new Request( + subject: $message->message->replyTo ?? throw new \RuntimeException('Cannot acknowledge: message has no reply subject'), + ), $messages); + $this->conn->requestBatch($requests, static function (int $index, Message|\Throwable $result) use ($confirmed): void { + $confirmed($index, $result instanceof \Throwable ? $result : null); + }, $timeout ?? 5.0); + } + // --- Stream Management --- public function createStream(StreamConfig $config): Stream diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index e2b73b743b2..7a8576ef43b 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -17,6 +17,12 @@ public function __construct( private readonly Transport $transport, ) {} + /** Whether received bytes remain to be parsed before another transport read. */ + public function hasBufferedData(): bool + { + return $this->buffer !== ''; + } + /** * Read and parse the next server operation. * diff --git a/src/Request.php b/src/Request.php new file mode 100644 index 00000000000..d60daadad41 --- /dev/null +++ b/src/Request.php @@ -0,0 +1,20 @@ +]/', $subject) + || str_starts_with($subject, '.') || str_ends_with($subject, '.') + || str_contains($subject, '..')) { + throw new \InvalidArgumentException('Invalid request subject'); + } + } +} diff --git a/tests/E2E/JetStreamExtraTest.php b/tests/E2E/JetStreamExtraTest.php index 177af0b8d29..cbf6039a739 100644 --- a/tests/E2E/JetStreamExtraTest.php +++ b/tests/E2E/JetStreamExtraTest.php @@ -161,6 +161,34 @@ public function testAckSyncConfirmsAndPreventsRedelivery(): void $this->assertCount(0, $again->getMessages(), 'acked message must not be redelivered'); } + public function testAckBatchConfirmsOnlySelectedMessages(): void + { + $id = uniqid(); + $subject = "ackbatch.{$id}"; + $stream = $this->createStream($subject); + foreach (['first', 'unfinished', 'third'] as $data) { + $this->js->publish($subject, $data); + } + $consumer = $this->js->createConsumer($stream, new ConsumerConfig( + durableName: 'c_' . $id, + ackPolicy: AckPolicy::Explicit, + ackWait: 0.2, + )); + $messages = $consumer->fetch(3, 2.0)->getMessages(); + $this->assertCount(3, $messages); + $confirmed = []; + $this->js->ackBatch([$messages[0], $messages[2]], static function (int $index, ?\Throwable $error) use (&$confirmed): void { + $confirmed[$index] = $error; + }); + ksort($confirmed); + $this->assertSame([null, null], $confirmed); + usleep(300_000); + $remaining = $consumer->fetch(3, 0.5, noWait: true)->getMessages(); + $this->assertCount(1, $remaining); + $this->assertSame('unfinished', $remaining[0]->getData()); + $remaining[0]->ackSync(); + } + public function testMetadataStreamOpsAndNumPending(): void { $id = uniqid(); diff --git a/tests/E2E/RequestsTest.php b/tests/E2E/RequestsTest.php new file mode 100644 index 00000000000..bbedf36b159 --- /dev/null +++ b/tests/E2E/RequestsTest.php @@ -0,0 +1,46 @@ +set('Trace', 'example'); + $received = []; + $connection->subscribe($subject, static function (Message $message) use ($connection, &$received): void { + $received[] = $message; + $connection->publish($message->replyTo, $message->data); + }); + $connection->flush(); + $results = []; + $connection->requestBatch([ + new Request(subject: $subject, data: 'first', headers: $headers), + new Request(subject: $subject . '.missing'), + new Request(subject: $subject, data: 'third'), + ], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }); + $this->assertCount(3, $results); + $this->assertSame('first', $results[0]->data); + $this->assertInstanceOf(NatsException::class, $results[1]); + $this->assertSame('third', $results[2]->data); + $this->assertSame('example', $received[0]->headers->get('Trace')); + } finally { + $connection->close(); + } + } +} diff --git a/tests/Unit/RequestsTest.php b/tests/Unit/RequestsTest.php new file mode 100644 index 00000000000..d443ad5a712 --- /dev/null +++ b/tests/Unit/RequestsTest.php @@ -0,0 +1,448 @@ + $fake)); + $fake->onWrite = static function (string $wire, FakeTransport $fake): void { + if (str_starts_with($wire, 'PUB ')) { + $fake->pushInbound("PING\r\n"); + } elseif ($wire === "PONG\r\n") { + throw new ConnectionException('PONG write failed'); + } + }; + $results = []; + $connection->requestBatch([new Request(subject: 'one'), new Request(subject: 'two')], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }); + $this->assertCount(2, $results); + $this->assertInstanceOf(ConnectionException::class, $results[0]); + $this->assertSame($results[0], $results[1]); + $this->assertFalse($connection->isConnected()); + $connection->close(); + } + + #[DataProvider('reconnect')] + public function testClosingErrorDisconnectsBeforeThrowingCallback(bool $reconnect): void + { + $fake = new FakeTransport(); + $failure = new ConnectionException('Application callback failed'); + $connection = null; + $connection = Connection::connect(new ConnectionOptions( + allowReconnect: $reconnect, + onError: function () use (&$connection, $failure): never { + $this->assertFalse($connection->isConnected()); + throw $failure; + }, + transportFactory: fn(): FakeTransport => $fake, + )); + $fake->onWrite = static function (string $wire, FakeTransport $fake): void { + if (str_starts_with($wire, 'PUB ')) { + $fake->pushInbound("-ERR 'Stale Connection'\r\n"); + } + }; + $callbacks = 0; + try { + $connection->requestBatch([new Request('one'), new Request('two')], static function () use (&$callbacks): void { + $callbacks++; + }); + $this->fail('Expected the callback exception'); + } catch (\Throwable $error) { + $this->assertSame($failure, $error); + } + $this->assertFalse($connection->isConnected()); + $this->assertSame(0, $callbacks); + $this->assertSame(1, substr_count($fake->written, 'PUB one ')); + $this->assertSame(1, substr_count($fake->written, 'PUB two ')); + $connection->close(); + } + + public static function bufferedReplies(): iterable + { + yield [false, false]; + yield [false, true]; + yield [true, false]; + yield [true, true]; + } + + #[DataProvider('bufferedReplies')] + public function testBufferedPongAndRepliesAreReadBeforeKeepalive(bool $reconnect, bool $pongLast): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions( + allowReconnect: $reconnect, + pingInterval: 0.0, + maxPingsOut: 1, + transportFactory: fn(): FakeTransport => $fake, + )); + $fake->answerPings = false; + $subjects = []; + $fake->onWrite = static function (string $wire, FakeTransport $fake) use (&$subjects, $pongLast): void { + if (str_starts_with($wire, 'PUB ')) { + preg_match_all('/PUB work ([^ ]+) 0\r\n\r\n/', $wire, $matches); + $subjects = $matches[1]; + } elseif ($wire === "PING\r\n") { + // One transport read buffers +OK, the PONG, and both replies. + $fake->pushInbound($pongLast ? "+OK\r\n" : "+OK\r\nPONG\r\n"); + foreach ($subjects as $subject) { + $fake->pushInbound("MSG {$subject} 1 2\r\nok\r\n"); + } + if ($pongLast) { + $fake->pushInbound("PONG\r\n"); + } + } + }; + $results = []; + $connection->requestBatch([new Request('work'), new Request('work')], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }); + $this->assertCount(2, $results); + foreach ($results as $result) { + $this->assertInstanceOf(Message::class, $result); + $this->assertSame('ok', $result->data); + } + $this->assertTrue($connection->isConnected()); + $this->assertSame(2, substr_count($fake->written, 'PUB work ')); + $this->assertSame('ok', $connection->request('work')->data); + $connection->close(); + } + + public static function keepalive(): iterable + { + yield 'request, silent peer' => [false, false]; + yield 'batch, silent peer' => [true, false]; + yield 'request, failed ping' => [false, true]; + yield 'batch, failed ping' => [true, true]; + } + + #[DataProvider('keepalive')] + public function testKeepaliveFailureDoesNotReplayRequests(bool $batch, bool $failedWrite): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions( + allowReconnect: true, + maxReconnectAttempts: 1, + pingInterval: 0.0, + maxPingsOut: 1, + transportFactory: fn(): FakeTransport => $fake, + )); + $fake->answerPings = false; + $before = substr_count($fake->written, "PING\r\n"); + if ($failedWrite) { + $fake->onWrite = static function (string $wire): void { + if ($wire === "PING\r\n") { + throw new ConnectionException('PING write failed'); + } + }; + } + $outcomes = []; + for ($attempt = 0; $attempt < ($failedWrite ? 1 : 2); $attempt++) { + try { + if ($batch) { + $connection->requestBatch([new Request('work')], static function (int $index, Message|\Throwable $result) use (&$outcomes): void { + $outcomes[] = $result; + }, 0.01); + } else { + $connection->request('work', timeout: 0.01); + } + } catch (ConnectionException $error) { + $outcomes[] = $error; + } + } + $this->assertCount($failedWrite ? 1 : 2, $outcomes); + $this->assertInstanceOf(ConnectionException::class, $outcomes[array_key_last($outcomes)]); + $this->assertNotInstanceOf(TimeoutException::class, $outcomes[array_key_last($outcomes)]); + $this->assertSame($before + 1, substr_count($fake->written, "PING\r\n")); + $this->assertSame(1, substr_count($fake->written, 'PUB work ')); + $this->assertFalse($connection->isConnected()); + $connection->close(); + } + + public function testPipelinesRequestsAndRetainsOutOfOrderPartialReplies(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $groups = []; + $fake->onWrite = static function (string $wire, FakeTransport $fake) use (&$groups): void { + preg_match_all('/PUB work\.(\d+) ([^ ]+) 0\r\n\r\n/', $wire, $matches, PREG_SET_ORDER); + if ($matches === []) { + return; + } + $groups[] = \count($matches); + foreach (array_reverse($matches) as $match) { + if ($match[1] !== '2') { + $fake->pushInbound("MSG {$match[2]} 1 1\r\n{$match[1]}\r\n"); + } + } + }; + $results = []; + $connection->requestBatch([ + new Request(subject: 'work.1'), new Request(subject: 'work.2'), new Request(subject: 'work.3'), + ], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }, 0.01); + $this->assertSame([3], $groups, 'all requests are sent before waiting for replies'); + $this->assertSame('1', $results[0]->data); + $this->assertInstanceOf(TimeoutException::class, $results[1]); + $this->assertSame('3', $results[2]->data); + $this->assertSame([2, 0, 1], array_keys($results), 'successful replies arrive before the missing reply times out'); + $this->assertSame('1', $connection->request('work.1')->data); + $connection->close(); + } + + public function testSubscriptionCallbackExceptionIsNotATransportFailure(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $failure = new ConnectionException('Application callback failed'); + $connection->subscribe('events', static fn() => throw $failure); + $fake->onWrite = static function (string $wire, FakeTransport $fake): void { + if (str_starts_with($wire, 'PUB ')) { + $fake->pushInbound("MSG events 1 1\r\nx\r\n"); + } + }; + $called = false; + try { + $connection->requestBatch([new Request('work')], static function () use (&$called): void { + $called = true; + }); + self::fail('Application exception must propagate'); + } catch (ConnectionException $error) { + $this->assertSame($failure, $error); + } + $this->assertFalse($called); + $this->assertTrue($connection->isConnected()); + $connection->close(); + } + + public function testSingleAndBatchRequestsShareSubjectValidation(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $before = $fake->written; + $refused = 0; + foreach (['', 'bad subject', 'work.*', 'work..x'] as $subject) { + foreach ([fn(): \Utopia\NATS\Request => new Request($subject), fn(): \Utopia\NATS\Message => $connection->request($subject)] as $create) { + try { + $create(); + self::fail('Invalid subject must be refused'); + } catch (\InvalidArgumentException) { + $refused++; + } + } + } + $this->assertSame(8, $refused); + $this->assertSame($before, $fake->written); + $connection->close(); + } + + public function testAmbiguousWriteIsNotReplayedAndDisconnects(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $writes = 0; + $fake->onWrite = static function (string $wire) use (&$writes): void { + if (str_starts_with($wire, 'PUB ')) { + $writes++; + throw new ConnectionException('Lost connection after write'); + } + }; + $results = []; + $connection->requestBatch([new Request(subject: 'first'), new Request(subject: 'second')], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }); + $this->assertSame(1, $writes); + $this->assertInstanceOf(ConnectionException::class, $results[0]); + $this->assertInstanceOf(ConnectionException::class, $results[1]); + $this->assertFalse($connection->isConnected()); + $connection->close(); + } + + public function testCallbackFailureAbortsAndLeavesConnectionUsable(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(subPendingMsgsLimit: 1, onSlowConsumer: static fn() => self::fail('Late replies must not accumulate'), transportFactory: fn(): FakeTransport => $fake)); + $this->respond($fake); + $seen = []; + // Even transport-shaped callback errors must not become request failures. + $failure = new ConnectionException('Callback failed'); + try { + $connection->requestBatch(array_fill(0, 4, new Request(subject: 'work')), static function (int $index, Message|\Throwable $result) use (&$seen, $failure): never { + $seen[$index] = $result; + throw $failure; + }); + self::fail('Callback error must be surfaced'); + } catch (ConnectionException $error) { + $this->assertSame($failure, $error); + } + $this->assertSame([0], array_keys($seen)); + $this->assertSame('ok', $connection->request('work')->data); + $connection->close(); + } + + public function testHeadersAndNoRespondersRetainIndependentOutcomes(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $observed = ''; + $fake->onWrite = static function (string $wire, FakeTransport $fake) use (&$observed): void { + if (preg_match('/HPUB work ([^ ]+) (\d+) (\d+)\r\n/', $wire, $match)) { + $observed = $wire; + $fake->pushInbound("MSG {$match[1]} 1 2\r\nok\r\n"); + } + if (preg_match('/PUB missing ([^ ]+) 0\r\n/', $wire, $match)) { + $header = "NATS/1.0 503\r\n\r\n"; + $length = \strlen($header); + $fake->pushInbound("HMSG {$match[1]} 1 {$length} {$length}\r\n{$header}\r\n"); + } + }; + $headers = new Headers(); + $headers->set('Trace', 'example'); + $results = []; + $connection->requestBatch([ + new Request(subject: 'work', data: 'payload', headers: $headers), + new Request(subject: 'missing'), + ], static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + }); + $this->assertStringContainsString("Trace: example\r\n", $observed); + $this->assertStringContainsString("\r\npayload\r\n", $observed); + $this->assertSame('ok', $results[0]->data); + $this->assertInstanceOf(NatsException::class, $results[1]); + $connection->close(); + } + + public static function invalid(): iterable + { + yield 'array instead of Request' => [[['subject' => 'work']], 1.0]; + yield 'not a list' => [['key' => new Request('work')], 1.0]; + yield 'timeout' => [[new Request('work')], 0.0]; + yield 'infinite timeout' => [[new Request('work')], INF]; + yield 'invalid later request' => [[new Request('work'), null], 1.0]; + } + + #[DataProvider('invalid')] + public function testInvalidInputDoesNotWriteOrInvokeCallbacks(array $requests, float $timeout): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $before = $fake->written; + $called = false; + try { + $connection->requestBatch($requests, static function () use (&$called): void { + $called = true; + }, $timeout); + self::fail('Invalid input must throw'); + } catch (\InvalidArgumentException) { + } + $this->assertSame($before, $fake->written); + $this->assertFalse($called); + $connection->close(); + } + + public function testEmptyInputNeedsNoConnection(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection->close(); + $before = $fake->written; + $connection->requestBatch([], static fn() => self::fail('Empty batch must not invoke callback')); + $this->assertSame($before, $fake->written); + } + + public function testOversizedLaterRequestDoesNotPublishEarlierRequests(): void + { + $fake = new FakeTransport(['max_payload' => 16]); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + try { + $connection->requestBatch([ + new Request(subject: 'work'), new Request(subject: 'work', data: str_repeat('x', 17)), + ], static fn() => self::fail('Validation must not invoke callback')); + self::fail('Payload validation must throw'); + } catch (MaxPayloadException) { + } + $this->assertStringNotContainsString('PUB ', $fake->written); + $this->respond($fake); + $this->assertSame('ok', $connection->request('work')->data); + $connection->close(); + } + + public function testCallbackCannotReadConnectionReentrantly(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $this->respond($fake); + $refused = 0; + $connection->requestBatch([new Request(subject: 'work')], function () use ($connection, &$refused): void { + foreach ([fn(): \Utopia\NATS\Message => $connection->request('work'), $connection->processMessage(...), fn(): array => $connection->requestMany('work'), fn() => $connection->wait(1), $connection->flush(...), $connection->tick(...), $connection->drain(...), fn() => $connection->requestBatch([], static fn() => null)] as $read) { + try { + $read(); + self::fail('Nested read must be refused'); + } catch (\LogicException) { + $refused++; + } + } + }); + $this->assertSame(8, $refused); + $this->assertSame('ok', $connection->request('work')->data); + $connection->close(); + } + + public function testSharedDeadlineStartsAfterWriteAndIncludesCallbacks(): void + { + $fake = new FakeTransport(); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $this->respond($fake); + $respond = $fake->onWrite; + $fake->onWrite = static function (string $wire, FakeTransport $fake) use ($respond): void { + usleep(30_000); + $respond($wire, $fake); + }; + $results = []; + $connection->requestBatch(array_fill(0, 2, new Request(subject: 'work')), static function (int $index, Message|\Throwable $result) use (&$results): void { + $results[$index] = $result; + if ($index === 0) { + usleep(30_000); + } + }, 0.01); + $this->assertSame('ok', $results[0]->data, 'writing does not consume the response timeout'); + $this->assertInstanceOf(TimeoutException::class, $results[1], 'callbacks do not reset the shared deadline'); + $connection->close(); + } + + private function respond(FakeTransport $fake): void + { + $fake->onWrite = static function (string $wire, FakeTransport $fake): void { + preg_match_all('/PUB work ([^ ]+) 0\r\n\r\n/', $wire, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $fake->pushInbound("MSG {$match[1]} 1 2\r\nok\r\n"); + } + }; + } +} diff --git a/tests/Unit/Support/FakeTransport.php b/tests/Unit/Support/FakeTransport.php index 65db3e326c6..83266dcd666 100644 --- a/tests/Unit/Support/FakeTransport.php +++ b/tests/Unit/Support/FakeTransport.php @@ -17,6 +17,7 @@ final class FakeTransport implements Transport { public string $written = ''; + public ?\Closure $onWrite = null; /** @var list */ public array $writes = []; /** @var list> */ @@ -45,6 +46,9 @@ public function write(string $data): int { $this->written .= $data; $this->writes[] = $data; + if ($this->onWrite instanceof \Closure) { + ($this->onWrite)($data, $this); + } // Answer PINGs so the handshake / flush / drain barrier completes. if ($this->answerPings) { From 672419e37cf77352b5b89beb658fd6ae7cb43d3a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 24 Sep 2026 21:44:02 +0530 Subject: [PATCH 404/470] test: add failing E2E tests proving JWT session null bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under JWT authentication, \ resolves to null via sessionVerify() because JWT auth never populates \'s cookie secret. This silently bypasses checks in 4 places in production today: 1. MFA recency check — JWT requests bypass MFA recency enforcement 2. MFA factor-count check — JWT requests bypass factor count enforcement 3. deleteSession('current') — returns 404, wrong session resolved 4. getSession('current') — returns 404, wrong session resolved Tests assert expected behavior — all currently fail, confirming the bug. Fix will thread JWT-resolved session ID through from request.php auth resolution so all 4 spots benefit from a single change. Draft PR — tests only --- .../Account/AccountCustomClientTest.php | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 72890ade540..168392ade3b 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -5281,6 +5281,212 @@ public function testMFAAuthenticatorCompletesEmailPasswordSession(): void $this->assertEquals($data['id'], $account['body']['$id']); } + public function testMFARecencyCheckBypassedUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $headers = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], + ]; + + $recoveryCodes = $this->client->call(Client::METHOD_POST, '/account/mfa/recovery-codes', $headers); + $this->assertEquals(201, $recoveryCodes['headers']['status-code']); + + // Cookie auth: correctly blocked, no recent MFA challenge on this session. + $cookieAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $headers); + $this->assertEquals(401, $cookieAttempt['headers']['status-code']); + $this->assertEquals('user_challenge_required', $cookieAttempt['body']['type']); + + // Same session, converted to a JWT (still no challenge completed). + $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $headers); + $this->assertEquals(201, $jwtResponse['headers']['status-code']); + $jwtHeaders = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], + ]; + + // EXPECTED: identical 401 user_challenge_required — nothing about + // the underlying session state changed, only the auth transport. + // ACTUAL (bug): the 'session' resource can't resolve under JWT, so + // this check is never correctly evaluated. + $jwtAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $jwtHeaders); + $this->assertEquals(401, $jwtAttempt['headers']['status-code']); + $this->assertEquals('user_challenge_required', $jwtAttempt['body']['type']); + } + + public function testMFAFactorCountBypassedUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $headers = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], + ]; + + $authenticator = $this->client->call(Client::METHOD_POST, '/account/mfa/authenticators/totp', $headers); + $this->assertEquals(200, $authenticator['headers']['status-code']); + + $totp = \OTPHP\TOTP::create($authenticator['body']['secret']); + $verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/authenticators/totp', $headers, [ + 'otp' => $totp->now(), + ]); + $this->assertEquals(200, $verification['headers']['status-code']); + + $mfa = $this->client->call(Client::METHOD_PATCH, '/account/mfa', $headers, ['mfa' => true]); + $this->assertEquals(200, $mfa['headers']['status-code']); + + // Fresh password-only login — factors: ['password'], below the + // 2-factor minimum now that MFA + a verified authenticator exist. + $freshSession = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => $data['email'], + 'password' => $data['password'], + ]); + $this->assertEquals(201, $freshSession['headers']['status-code']); + $this->assertEquals(['password'], $freshSession['body']['factors']); + + $freshCookieHeaders = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $freshSession['cookies']['a_session_' . $projectId], + ]; + + // Cookie auth: correctly blocked, only 1 of 2 required factors present. + $cookieAttempt = $this->client->call(Client::METHOD_GET, '/account', $freshCookieHeaders); + $this->assertEquals(401, $cookieAttempt['headers']['status-code']); + $this->assertEquals('user_more_factors_required', $cookieAttempt['body']['type']); + + // Same under-verified session, converted to a JWT. + $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $freshCookieHeaders); + $this->assertEquals(201, $jwtResponse['headers']['status-code']); + $jwtHeaders = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], + ]; + + // EXPECTED: identical 401 user_more_factors_required. + // ACTUAL (bug): shared/api.php guards this check with `if ($session && ...)` + // — under JWT, $session is null, so the check is silently skipped + // and the request succeeds despite the missing second factor. + $jwtAttempt = $this->client->call(Client::METHOD_GET, '/account', $jwtHeaders); + $this->assertEquals(401, $jwtAttempt['headers']['status-code']); + $this->assertEquals('user_more_factors_required', $jwtAttempt['body']['type']); + } + + public function testDeleteSessionCurrentWrongUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $sessionAId = $data['sessionId']; + $sessionA = $data['session']; + + $sessionBResponse = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => $data['email'], + 'password' => $data['password'], + ]); + $this->assertEquals(201, $sessionBResponse['headers']['status-code']); + $sessionB = $sessionBResponse['cookies']['a_session_' . $projectId]; + + // Get a JWT backed by session A specifically. + $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $sessionA, + ]); + $this->assertEquals(201, $jwtResponse['headers']['status-code']); + $jwtHeaders = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], + ]; + + // EXPECTED: 'current' resolves to session A (the JWT-backing + // session) and deletes it — 204, matching what a cookie-authenticated + // caller gets from the same "current" keyword. + // ACTUAL (bug): sessionVerify() can't resolve under JWT, so the + // lookup loop never matches any session, and this throws + // 404 user_session_not_found instead of deleting session A. + $delete = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', $jwtHeaders); + $this->assertEquals(204, $delete['headers']['status-code']); + + // Session A should now be gone... + $checkA = $this->client->call(Client::METHOD_GET, '/account', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $sessionA, + ]); + $this->assertEquals(401, $checkA['headers']['status-code']); + + // ...and session B should be untouched. + $checkB = $this->client->call(Client::METHOD_GET, '/account', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $sessionB, + ]); + $this->assertEquals(200, $checkB['headers']['status-code']); + + // Clean up. + $this->client->call(Client::METHOD_DELETE, '/account/sessions', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $sessionB, + ]); + } + + public function testGetSessionCurrentWrongUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], + ]); + $this->assertEquals(201, $jwtResponse['headers']['status-code']); + $jwtHeaders = [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], + ]; + + // EXPECTED: 'current' resolves to the JWT-backing session and + // returns it — 200, matching cookie-auth behavior for the same + // "current" keyword. + // ACTUAL (bug): sessionVerify() can't resolve under JWT, so this + // throws 404 user_session_not_found instead of returning the session. + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', $jwtHeaders); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($data['sessionId'], $response['body']['$id']); + } + public function testRefreshEmailPasswordSession(): void { $email = uniqid() . 'user@localhost.test'; From 9b55e3ec649c8d05a13cdfac98d6456ba9a6562d Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 25 Sep 2026 08:11:35 +0530 Subject: [PATCH 405/470] fix tests: correct MFA factor-count JWT setup and recency description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - testMFAFactorCountBypassedUnderJWT: create JWT from session B before enabling MFA so JWT creation is not blocked by the factor-count check - testMFARecencyCheckFailsUnderJWT: rename from Bypassed — request errors with TypeError 500 because session resolves to null under JWT Addresses Greptile P1 and P2 findings. --- .../Account/AccountCustomClientTest.php | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 168392ade3b..1a737d8764c 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -5281,7 +5281,7 @@ public function testMFAAuthenticatorCompletesEmailPasswordSession(): void $this->assertEquals($data['id'], $account['body']['$id']); } - public function testMFARecencyCheckBypassedUnderJWT(): void + public function testMFARecencyCheckFailsUnderJWT(): void { $data = $this->createFreshAccountWithSession(); $projectId = $this->getProject()['$id']; @@ -5313,8 +5313,9 @@ public function testMFARecencyCheckBypassedUnderJWT(): void // EXPECTED: identical 401 user_challenge_required — nothing about // the underlying session state changed, only the auth transport. - // ACTUAL (bug): the 'session' resource can't resolve under JWT, so - // this check is never correctly evaluated. + // ACTUAL (bug): the 'session' resource resolves to null under JWT, and + // the mfaProtected middleware requires a non-null Document, so the + // request errors out instead of evaluating recency. $jwtAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $jwtHeaders); $this->assertEquals(401, $jwtAttempt['headers']['status-code']); $this->assertEquals('user_challenge_required', $jwtAttempt['body']['type']); @@ -5332,21 +5333,8 @@ public function testMFAFactorCountBypassedUnderJWT(): void 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], ]; - $authenticator = $this->client->call(Client::METHOD_POST, '/account/mfa/authenticators/totp', $headers); - $this->assertEquals(200, $authenticator['headers']['status-code']); - - $totp = \OTPHP\TOTP::create($authenticator['body']['secret']); - $verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/authenticators/totp', $headers, [ - 'otp' => $totp->now(), - ]); - $this->assertEquals(200, $verification['headers']['status-code']); - - $mfa = $this->client->call(Client::METHOD_PATCH, '/account/mfa', $headers, ['mfa' => true]); - $this->assertEquals(200, $mfa['headers']['status-code']); - - // Fresh password-only login — factors: ['password'], below the - // 2-factor minimum now that MFA + a verified authenticator exist. - $freshSession = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + // Second password-only session, created while MFA is still off. + $sessionB = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, @@ -5354,24 +5342,40 @@ public function testMFAFactorCountBypassedUnderJWT(): void 'email' => $data['email'], 'password' => $data['password'], ]); - $this->assertEquals(201, $freshSession['headers']['status-code']); - $this->assertEquals(['password'], $freshSession['body']['factors']); + $this->assertEquals(201, $sessionB['headers']['status-code']); + $this->assertEquals(['password'], $sessionB['body']['factors']); - $freshCookieHeaders = [ + $sessionBHeaders = [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $freshSession['cookies']['a_session_' . $projectId], + 'cookie' => 'a_session_' . $projectId . '=' . $sessionB['cookies']['a_session_' . $projectId], ]; - // Cookie auth: correctly blocked, only 1 of 2 required factors present. - $cookieAttempt = $this->client->call(Client::METHOD_GET, '/account', $freshCookieHeaders); + // The JWT must be issued now: /account/jwts runs the same factor-count + // check, so it would be refused once session B is under-verified. + $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $sessionBHeaders); + $this->assertEquals(201, $jwtResponse['headers']['status-code']); + + // Enable MFA from session A. Only the calling session gains the totp + // factor; session B stays at ['password'], below the new 2-factor minimum. + $authenticator = $this->client->call(Client::METHOD_POST, '/account/mfa/authenticators/totp', $headers); + $this->assertEquals(200, $authenticator['headers']['status-code']); + + $totp = \OTPHP\TOTP::create($authenticator['body']['secret']); + $verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/authenticators/totp', $headers, [ + 'otp' => $totp->now(), + ]); + $this->assertEquals(200, $verification['headers']['status-code']); + + $mfa = $this->client->call(Client::METHOD_PATCH, '/account/mfa', $headers, ['mfa' => true]); + $this->assertEquals(200, $mfa['headers']['status-code']); + + // Cookie auth on session B: correctly blocked, only 1 of 2 required factors present. + $cookieAttempt = $this->client->call(Client::METHOD_GET, '/account', $sessionBHeaders); $this->assertEquals(401, $cookieAttempt['headers']['status-code']); $this->assertEquals('user_more_factors_required', $cookieAttempt['body']['type']); - // Same under-verified session, converted to a JWT. - $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $freshCookieHeaders); - $this->assertEquals(201, $jwtResponse['headers']['status-code']); $jwtHeaders = [ 'origin' => 'http://localhost', 'content-type' => 'application/json', From 0897f98130e5097b174b998fb85fa023c53343ab Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 28 Sep 2026 17:31:42 +0530 Subject: [PATCH 406/470] fix(teams): accept 81 character roles on create createTeam validated roles with a bare Key(), capping them at 36 chars while createMembership and updateMembership already allow 81, so a role accepted on a membership was rejected on the team that owns it. The param description also claimed 32. --- .../Platform/Modules/Teams/Http/Teams/Create.php | 2 +- tests/e2e/Services/Teams/TeamsBase.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php b/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php index 72c8add2969..911fdf403af 100644 --- a/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php +++ b/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php @@ -62,7 +62,7 @@ public function __construct() )) ->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.') - ->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true) + ->param('roles', ['owner'], new ArrayList(new Key(maxLength: 81), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 81 characters long.', true) // For project-specific permissions, roles will be in the format `project--`. Template takes 9 characters, `projectId` and `role` can be upto 36 characters. In total, 81 characters. ->inject('response') ->inject('user') ->inject('dbForProject') diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 413438cd824..7c219abf52a 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -166,6 +166,18 @@ public function testCreateTeam(): void $this->assertIsInt($response3['body']['total']); $this->assertEquals(true, $dateValidator->isValid($response3['body']['$createdAt'])); + // A project-scoped role fills the full `project--` budget of 81 chars + $response4 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Aston Villa', + 'roles' => [\str_repeat('a', 81)], + ]); + + $this->assertEquals(201, $response4['headers']['status-code']); + /** * Test for FAILURE */ From c7779354f9854f1079f52f3524c24280bb370916 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 28 Sep 2026 17:39:34 +0530 Subject: [PATCH 407/470] feat(oauth2): add Webflow provider Sign in with a Webflow account through the authorization code flow: authorize at webflow.com/oauth/authorize, exchange the code at api.webflow.com/oauth/access_token, and read the account from /v2/token/authorized_by under the authorized_user:read scope. Webflow returns no refresh token and exposes no refresh grant, so refreshTokens has nothing to exchange. --- app/config/oAuthProviders.php | 11 ++ app/init/models.php | 2 + src/Appwrite/Auth/OAuth2/Webflow.php | 162 ++++++++++++++++++ .../Project/Http/Project/OAuth2/Base.php | 1 + .../Http/Project/OAuth2/Webflow/Update.php | 55 ++++++ .../Modules/Project/Services/Http.php | 2 + src/Appwrite/Utopia/Response.php | 1 + .../Utopia/Response/Model/OAuth2Webflow.php | 47 +++++ 8 files changed, 281 insertions(+) create mode 100644 src/Appwrite/Auth/OAuth2/Webflow.php create mode 100644 src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php create mode 100644 src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php diff --git a/app/config/oAuthProviders.php b/app/config/oAuthProviders.php index c6fe102c54a..4de082b6740 100644 --- a/app/config/oAuthProviders.php +++ b/app/config/oAuthProviders.php @@ -478,6 +478,17 @@ 'mock' => false, 'class' => 'Appwrite\\Auth\\OAuth2\\Twitch', ], + 'webflow' => [ + 'name' => 'Webflow', + 'developers' => 'https://developers.webflow.com/data/reference/oauth-app', + 'icon' => 'icon-webflow', + 'enabled' => true, + 'sandbox' => false, + 'form' => false, + 'beta' => false, + 'mock' => false, + 'class' => 'Appwrite\\Auth\\OAuth2\\Webflow', + ], 'wordpress' => [ 'name' => 'WordPress', 'developers' => 'https://developer.wordpress.com/docs/oauth2/', diff --git a/app/init/models.php b/app/init/models.php index 2eb36ce9644..6de922696b5 100644 --- a/app/init/models.php +++ b/app/init/models.php @@ -157,6 +157,7 @@ use Appwrite\Utopia\Response\Model\OAuth2TikTok; use Appwrite\Utopia\Response\Model\OAuth2Tradeshift; use Appwrite\Utopia\Response\Model\OAuth2Twitch; +use Appwrite\Utopia\Response\Model\OAuth2Webflow; use Appwrite\Utopia\Response\Model\OAuth2WordPress; use Appwrite\Utopia\Response\Model\OAuth2X; use Appwrite\Utopia\Response\Model\OAuth2Yahoo; @@ -434,6 +435,7 @@ Response::setModel(new OAuth2Zoho()); Response::setModel(new OAuth2Yandex()); Response::setModel(new OAuth2X()); +Response::setModel(new OAuth2Webflow()); Response::setModel(new OAuth2WordPress()); Response::setModel(new OAuth2Twitch()); Response::setModel(new OAuth2Stripe()); diff --git a/src/Appwrite/Auth/OAuth2/Webflow.php b/src/Appwrite/Auth/OAuth2/Webflow.php new file mode 100644 index 00000000000..f87805ff452 --- /dev/null +++ b/src/Appwrite/Auth/OAuth2/Webflow.php @@ -0,0 +1,162 @@ + 'code', + 'client_id' => $this->appID, + 'redirect_uri' => $this->callback, + 'scope' => \implode(' ', $this->getScopes()), + 'state' => \json_encode($this->state) + ]); + } + + /** + * @param string $code + * + * @return array + */ + protected function getTokens(string $code): array + { + if (empty($this->tokens)) { + $this->tokens = \json_decode($this->request( + 'POST', + 'https://api.webflow.com/oauth/access_token', + ['Content-Type: application/x-www-form-urlencoded'], + \http_build_query([ + 'grant_type' => 'authorization_code', + 'client_id' => $this->appID, + 'client_secret' => $this->appSecret, + 'redirect_uri' => $this->callback, + 'code' => $code + ]) + ), true); + } + + return $this->tokens; + } + + /** + * Webflow issues no refresh token and exposes no refresh grant, so there + * is nothing to exchange. Sessions therefore never hold a Webflow refresh + * token and the session refresh path skips providers that have none. + * + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken): array + { + $this->tokens['refresh_token'] = $refreshToken; + + return $this->tokens; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserID(string $accessToken): string + { + $user = $this->getUser($accessToken); + + return $user['id'] ?? ''; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserEmail(string $accessToken): string + { + $user = $this->getUser($accessToken); + + return $user['email'] ?? ''; + } + + /** + * @param string $accessToken + * + * @return bool + */ + public function isEmailVerified(string $accessToken): bool + { + // Provider exposes no email verification signal, so treat as unverified until one is confirmed + return false; + } + + /** + * @param string $accessToken + * + * @return string + */ + public function getUserName(string $accessToken): string + { + $user = $this->getUser($accessToken); + + return \trim(($user['firstName'] ?? '') . ' ' . ($user['lastName'] ?? '')); + } + + /** + * @param string $accessToken + * + * @return array + */ + protected function getUser(string $accessToken): array + { + if (empty($this->user)) { + $this->user = \json_decode($this->request( + 'GET', + $this->resourceEndpoint, + ['Authorization: Bearer ' . $accessToken] + ), true); + } + + return $this->user; + } +} diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php index 036dbf185a8..4810fda9029 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php @@ -374,6 +374,7 @@ public static function getProviderActions(): array 'zoho' => Zoho\Update::class, 'yandex' => Yandex\Update::class, 'x' => X\Update::class, + 'webflow' => Webflow\Update::class, 'wordpress' => WordPress\Update::class, 'twitch' => Twitch\Update::class, 'stripe' => Stripe\Update::class, diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php new file mode 100644 index 00000000000..f3c11d9fb45 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php @@ -0,0 +1,55 @@ +addAction(UpdateOAuth2Zoho::getName(), new UpdateOAuth2Zoho()); $this->addAction(UpdateOAuth2Yandex::getName(), new UpdateOAuth2Yandex()); $this->addAction(UpdateOAuth2X::getName(), new UpdateOAuth2X()); + $this->addAction(UpdateOAuth2Webflow::getName(), new UpdateOAuth2Webflow()); $this->addAction(UpdateOAuth2WordPress::getName(), new UpdateOAuth2WordPress()); $this->addAction(UpdateOAuth2Twitch::getName(), new UpdateOAuth2Twitch()); $this->addAction(UpdateOAuth2Stripe::getName(), new UpdateOAuth2Stripe()); diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 60985c8145d..963e6e97e09 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -302,6 +302,7 @@ class Response extends SwooleResponse public const MODEL_OAUTH2_ZOHO = 'oAuth2Zoho'; public const MODEL_OAUTH2_YANDEX = 'oAuth2Yandex'; public const MODEL_OAUTH2_X = 'oAuth2X'; + public const MODEL_OAUTH2_WEBFLOW = 'oAuth2Webflow'; public const MODEL_OAUTH2_WORDPRESS = 'oAuth2WordPress'; public const MODEL_OAUTH2_TWITCH = 'oAuth2Twitch'; public const MODEL_OAUTH2_STRIPE = 'oAuth2Stripe'; diff --git a/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php b/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php new file mode 100644 index 00000000000..0fe40034de3 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php @@ -0,0 +1,47 @@ + 'webflow', + ]; + + public function getProviderLabel(): string + { + return 'Webflow'; + } + + public function getClientIdExample(): string + { + return '0d6a00000000000000000000000000000000000000000000000000000000f31c'; + } + + public function getClientSecretExample(): string + { + return '7b1e00000000000000000000000000000000000000000000000000000000ac95'; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'OAuth2Webflow'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_OAUTH2_WEBFLOW; + } +} From 165d9dc11f973836d8ec43c19e5cae97e0ae8098 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 28 Sep 2026 17:42:47 +0530 Subject: [PATCH 408/470] feat(messaging): allow filtering messages by users and targets The messages `users` and `targets` attributes are string arrays, so listing them in ALLOWED_ATTRIBUTES makes Query.contains usable on both and keeps the endpoint's query description in sync. --- .../Database/Validator/Queries/Messages.php | 2 ++ .../Validator/Queries/MessagesTest.php | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php index 10032a87455..c3fa2317f74 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Messages.php @@ -11,6 +11,8 @@ class Messages extends Base 'status', 'description', 'providerType', + 'users', + 'targets', ]; /** diff --git a/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php b/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php new file mode 100644 index 00000000000..6bccbcbac06 --- /dev/null +++ b/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php @@ -0,0 +1,28 @@ +assertEquals(true, $validator->isValid([Query::contains('users', ['someUserId'])]), $validator->getDescription()); + $this->assertEquals(true, $validator->isValid([Query::contains('targets', ['someTargetId'])]), $validator->getDescription()); + + /** + * Test for Failure + */ + $this->assertEquals(false, $validator->isValid([Query::contains('topics', ['someTopicId'])]), $validator->getDescription()); + } +} From 619b3cdf1bd3bf38b168b84478a9299a5c367543 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 28 Sep 2026 17:44:12 +0530 Subject: [PATCH 409/470] fix(detector): report ssr for TanStack Start apps that prerender a subset A `routes` list or a `filter` inside the prerender block only prerenders part of the site, so the server entry is still emitted and still needed. Only a prerender block with no such narrowing is treated as static. --- .../detector/src/Detection/Framework/TanStackStart.php | 5 +++++ packages/detector/tests/DetectorTest.php | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 74c1af06873..42418876c8f 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -66,6 +66,11 @@ public function getAdapter(string $configContent): string return 'ssr'; } + // A `routes` list or a `filter` narrows prerendering to part of the site, so the server entry is still built. + if (\preg_match('/\bprerender[\x27\x22]?\s*:\s*\{([^}]*)/', $stripped, $block) && \preg_match('/\b(?:routes|filter)[\x27\x22]?\s*:/', $block[1])) { + return 'ssr'; + } + return 'static'; } } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 37b5e898ea4..0ad442cd7c9 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -795,11 +795,14 @@ public function testTanStackStartAdapterDetection(): void $fw = new TanStackStart(); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); - $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart(), nitro({ config: { prerender: { routes: [\'/\'], crawlLinks: true } } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": { "filter": ({ path }) => path.startsWith(\'/docs\') } })] })')); + $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); - $this->assertSame('static', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); + $this->assertSame('ssr', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); $this->assertNotEmpty($fw->getConfigFiles()); } From 3bff3949d9d032b54a82ab4502dcd6adb89e9e53 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 28 Sep 2026 17:52:14 +0530 Subject: [PATCH 410/470] fix(oauth2): register Webflow in the provider list model The provider list resolves each entry by matching its $id against the candidate models named in OAuth2ProviderList, so a provider missing from that union makes GET /v1/project/oauth2 throw "Missing model for rule" for every project. Add Webflow there, and to the SDK response model list on the per-provider get so the generated spec carries its shape. Also place the entry after WordPress in the four descending registries it was inserted ahead of. --- app/init/models.php | 2 +- src/Appwrite/Auth/OAuth2/Webflow.php | 4 +--- .../Platform/Modules/Project/Http/Project/OAuth2/Base.php | 2 +- .../Platform/Modules/Project/Http/Project/OAuth2/Get.php | 1 + src/Appwrite/Platform/Modules/Project/Services/Http.php | 2 +- src/Appwrite/Utopia/Response.php | 2 +- src/Appwrite/Utopia/Response/Model/OAuth2ProviderList.php | 1 + 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/init/models.php b/app/init/models.php index 6de922696b5..b8d71d9a724 100644 --- a/app/init/models.php +++ b/app/init/models.php @@ -435,8 +435,8 @@ Response::setModel(new OAuth2Zoho()); Response::setModel(new OAuth2Yandex()); Response::setModel(new OAuth2X()); -Response::setModel(new OAuth2Webflow()); Response::setModel(new OAuth2WordPress()); +Response::setModel(new OAuth2Webflow()); Response::setModel(new OAuth2Twitch()); Response::setModel(new OAuth2Stripe()); Response::setModel(new OAuth2Spotify()); diff --git a/src/Appwrite/Auth/OAuth2/Webflow.php b/src/Appwrite/Auth/OAuth2/Webflow.php index f87805ff452..3fd5613f473 100644 --- a/src/Appwrite/Auth/OAuth2/Webflow.php +++ b/src/Appwrite/Auth/OAuth2/Webflow.php @@ -80,9 +80,7 @@ protected function getTokens(string $code): array } /** - * Webflow issues no refresh token and exposes no refresh grant, so there - * is nothing to exchange. Sessions therefore never hold a Webflow refresh - * token and the session refresh path skips providers that have none. + * Webflow exposes no refresh grant, so there is nothing to exchange. * * @param string $refreshToken * diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php index 4810fda9029..23e9304cd94 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php @@ -374,8 +374,8 @@ public static function getProviderActions(): array 'zoho' => Zoho\Update::class, 'yandex' => Yandex\Update::class, 'x' => X\Update::class, - 'webflow' => Webflow\Update::class, 'wordpress' => WordPress\Update::class, + 'webflow' => Webflow\Update::class, 'twitch' => Twitch\Update::class, 'stripe' => Stripe\Update::class, 'spotify' => Spotify\Update::class, diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Get.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Get.php index 2392734b6b2..d545355fb26 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Get.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Get.php @@ -58,6 +58,7 @@ public function __construct() Response::MODEL_OAUTH2_YANDEX, Response::MODEL_OAUTH2_X, Response::MODEL_OAUTH2_WORDPRESS, + Response::MODEL_OAUTH2_WEBFLOW, Response::MODEL_OAUTH2_TWITCH, Response::MODEL_OAUTH2_STRIPE, Response::MODEL_OAUTH2_SPOTIFY, diff --git a/src/Appwrite/Platform/Modules/Project/Services/Http.php b/src/Appwrite/Platform/Modules/Project/Services/Http.php index 182ce64636e..2c317fd6e93 100644 --- a/src/Appwrite/Platform/Modules/Project/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Project/Services/Http.php @@ -217,8 +217,8 @@ public function __construct() $this->addAction(UpdateOAuth2Zoho::getName(), new UpdateOAuth2Zoho()); $this->addAction(UpdateOAuth2Yandex::getName(), new UpdateOAuth2Yandex()); $this->addAction(UpdateOAuth2X::getName(), new UpdateOAuth2X()); - $this->addAction(UpdateOAuth2Webflow::getName(), new UpdateOAuth2Webflow()); $this->addAction(UpdateOAuth2WordPress::getName(), new UpdateOAuth2WordPress()); + $this->addAction(UpdateOAuth2Webflow::getName(), new UpdateOAuth2Webflow()); $this->addAction(UpdateOAuth2Twitch::getName(), new UpdateOAuth2Twitch()); $this->addAction(UpdateOAuth2Stripe::getName(), new UpdateOAuth2Stripe()); $this->addAction(UpdateOAuth2Spotify::getName(), new UpdateOAuth2Spotify()); diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 963e6e97e09..f88775cc64f 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -302,8 +302,8 @@ class Response extends SwooleResponse public const MODEL_OAUTH2_ZOHO = 'oAuth2Zoho'; public const MODEL_OAUTH2_YANDEX = 'oAuth2Yandex'; public const MODEL_OAUTH2_X = 'oAuth2X'; - public const MODEL_OAUTH2_WEBFLOW = 'oAuth2Webflow'; public const MODEL_OAUTH2_WORDPRESS = 'oAuth2WordPress'; + public const MODEL_OAUTH2_WEBFLOW = 'oAuth2Webflow'; public const MODEL_OAUTH2_TWITCH = 'oAuth2Twitch'; public const MODEL_OAUTH2_STRIPE = 'oAuth2Stripe'; public const MODEL_OAUTH2_SPOTIFY = 'oAuth2Spotify'; diff --git a/src/Appwrite/Utopia/Response/Model/OAuth2ProviderList.php b/src/Appwrite/Utopia/Response/Model/OAuth2ProviderList.php index 927efd6b830..c8c46d2fd8b 100644 --- a/src/Appwrite/Utopia/Response/Model/OAuth2ProviderList.php +++ b/src/Appwrite/Utopia/Response/Model/OAuth2ProviderList.php @@ -33,6 +33,7 @@ public function __construct() Response::MODEL_OAUTH2_YANDEX, Response::MODEL_OAUTH2_X, Response::MODEL_OAUTH2_WORDPRESS, + Response::MODEL_OAUTH2_WEBFLOW, Response::MODEL_OAUTH2_TWITCH, Response::MODEL_OAUTH2_STRIPE, Response::MODEL_OAUTH2_SPOTIFY, From d0e4ff5342c318b6f1be4622687dca2f91152eff Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Mon, 28 Sep 2026 15:41:21 +0200 Subject: [PATCH 411/470] fix(queue): dead-letter a message whose every attempt died, without the advisory Ported from utopia-php/monorepo#348 (d2a32f95, 0483014e), closed unmerged when queue moved into packages/queue here. A message whose every delivery died unacknowledged was left for the MAX_DELIVERIES advisory. That advisory is core NATS and is lost when no broker is subscribed, stranding the message on the work stream past max_deliver where num_pending and num_ack_pending both miss it. The work consumer now gets one spare delivery (max_deliver + 1), and receive() parks a delivery past maxDeliver on the dead stream before any codec or handler sees it. A Require broker reads the spare from the consumer the owner provisioned, and reports a consumer without one. The phpstan baseline entry for the anonymous transport in NatsBrokerTest moves with the line it pins. --- packages/queue/README.md | 4 +- packages/queue/phpstan-baseline.neon | 2 +- packages/queue/src/Broker/Nats.php | 67 ++++++++++++--- packages/queue/tests/E2E/NatsBrokerTest.php | 91 +++++++++++++++++++++ 4 files changed, 151 insertions(+), 13 deletions(-) diff --git a/packages/queue/README.md b/packages/queue/README.md index 1f4a854113c..24c80d8f803 100644 --- a/packages/queue/README.md +++ b/packages/queue/README.md @@ -94,7 +94,9 @@ $broker = new Nats( $broker->publish(new Queue('my-queue'), ['type' => 'test_number', 'value' => 123]); ``` -Each queue is a WorkQueue-retention stream (a message is removed once acknowledged) with a companion dead stream. `commit()` acknowledges a message, `reject()` schedules redelivery until `maxDeliver` and then dead-letters — unless the handler declared the failure permanent, which dead-letters it at once — `retry()` re-drives the dead stream onto the queue, and `getQueueSize()` reports pending (consumer `num_pending`) or failed (dead stream) counts. `reap()` is a no-op — redelivery after `ackWait` reclaims jobs stranded by a dead worker. Requires [`utopia-php/nats`](https://github.com/utopia-php/nats). +Each queue is a WorkQueue-retention stream (a message is removed once acknowledged) with a companion dead stream. `commit()` acknowledges a message, `reject()` schedules redelivery until `maxDeliver` and then dead-letters — unless the handler declared the failure permanent, which dead-letters it at once — `retry()` re-drives the dead stream onto the queue, and `getQueueSize()` reports pending (consumer `num_pending`) or failed (dead stream) counts. `reap()` is a no-op — redelivery after `ackWait` reclaims jobs stranded by a dead worker. + +The server-side consumer is created with `max_deliver` one above `maxDeliver`, so reading it off `/jsz` shows the extra delivery. A message whose every attempt died unacknowledged, with no `reject()`, arrives once more and `receive()` dead-letters it without running a handler. At exactly `maxDeliver`, JetStream would retire it silently. It would stay on the work stream, counted as neither pending nor in flight, and only reach the dead stream if a broker happened to be subscribed when the server announced it. The consumer carries a `utopia_queue_spare_delivery` metadata key saying so. A broker under `Provisioning::Require`, which never writes configuration, reads that key to find the spare delivery, and reports through `onError` if it adopts a consumer provisioned without one. Requires [`utopia-php/nats`](https://github.com/utopia-php/nats). ### Shaping a queue diff --git a/packages/queue/phpstan-baseline.neon b/packages/queue/phpstan-baseline.neon index 114818064b0..cf6fa3c54e8 100644 --- a/packages/queue/phpstan-baseline.neon +++ b/packages/queue/phpstan-baseline.neon @@ -985,7 +985,7 @@ parameters: path: tests/E2E/NatsBrokerTest.php - - message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/E2E/NatsBrokerTest\.php\:863\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/E2E/NatsBrokerTest\.php\:892\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue count: 1 path: tests/E2E/NatsBrokerTest.php diff --git a/packages/queue/src/Broker/Nats.php b/packages/queue/src/Broker/Nats.php index fa70461b4c0..7d3a7257871 100644 --- a/packages/queue/src/Broker/Nats.php +++ b/packages/queue/src/Broker/Nats.php @@ -96,6 +96,10 @@ class Nats implements Synchronous, Consumer, Bounded private const int MAX_STREAM_NAME = 255; private const string METADATA_IDENTITY = 'utopia_queue_identity'; + // Consumer-metadata key marking a work consumer provisioned with one delivery past + // maxDeliver, so a broker adopting it can tell that spare from a larger budget. + private const string METADATA_SPARE_DELIVERY = 'utopia_queue_spare_delivery'; + // Retry budget for first-time provisioning of a replicated stream. The window // doubles per attempt because a fixed one re-synchronises the losers: every process // that lost the first race waits the same interval and collides again. See ensure(). @@ -120,6 +124,13 @@ class Nats implements Synchronous, Consumer, Bounded /** @var array max-deliveries advisory subscription per queue */ private array $advisories = []; + /** + * @var array per queue, the delivery that is dead-lettered on + * arrival -- the server's last, when it allows one past maxDeliver; null when + * the consumer allows none, and exhaustion falls to the advisory alone + */ + private array $spareDelivery = []; + /** Publishes the stream recognised as duplicates of an id it already held. */ private int $duplicates = 0; @@ -724,7 +735,19 @@ private function pull(Queue $queue, int $timeout, int $max): array $messages = []; + $spare = $this->spareDelivery[$key] ?? null; + foreach ($deliveries as $jsMessage) { + // The server allows one delivery past maxDeliver (see provision()), and it + // only reaches here when no attempt before it ended in reject() -- a worker + // died holding each one. Dead-lettered on this delivery rather than by the + // advisory, which nobody receives while no broker is subscribed. + if ($spare !== null && $jsMessage->metadata()->numDelivered >= $spare) { + $this->park($queue, $jsMessage, 'max deliveries exceeded'); + + continue; + } + try { $data = $this->codec->decode($jsMessage->getData()); } catch (\Throwable) { @@ -735,7 +758,7 @@ private function pull(Queue $queue, int $timeout, int $max): array // Parked rather than thrown: in a batch a throw here would leave // every message behind it unregistered and unacknowledged, each // burning an attempt and a maxAckPending slot for a full ackWait. - $this->park($queue, $jsMessage); + $this->park($queue, $jsMessage, 'payload could not be decoded'); continue; } @@ -753,7 +776,8 @@ private function pull(Queue $queue, int $timeout, int $max): array } /** - * Set aside a message no codec on this worker can read. + * Set aside a message no handler should see: one no codec on this worker can + * read, or one past its last attempt. * * Straight to the dead stream and terminated, rather than NAK'd: every * redelivery would fail the same way, and unacknowledged it would hold a @@ -766,7 +790,7 @@ private function pull(Queue $queue, int $timeout, int $max): array * redelivery and ends on the dead stream anyway, where dropping the ack * first would lose it outright. */ - private function park(Queue $queue, JetStreamMessage $jsMessage): void + private function park(Queue $queue, JetStreamMessage $jsMessage, string $reason): void { try { // The message's own Content-Type, not this codec's: the bytes go over @@ -779,7 +803,7 @@ private function park(Queue $queue, JetStreamMessage $jsMessage): void } $this->js()->publish($this->deadSubject($queue), $jsMessage->getData(), headers: $headers); - $jsMessage->term('payload could not be decoded'); + $jsMessage->term($reason); } catch (\Throwable $error) { $this->report($error); } @@ -876,6 +900,7 @@ public function reconnect(): bool $this->commandsConsumers = []; $this->consumers = []; $this->advisories = []; + $this->spareDelivery = []; $this->provisioned = []; $this->inFlight = []; @@ -1288,20 +1313,25 @@ private function provision(Queue $queue, string $key): void durableName: self::CONSUMER_WORK, ackPolicy: AckPolicy::Explicit, ackWait: $this->ackWait, - maxDeliver: $this->maxDeliver, + // One more than the broker's own budget. reject() dead-letters on the + // maxDeliver-th failure itself; the spare delivery is for the message whose + // every attempt died unacknowledged, which pull() dead-letters on arrival. + // At exactly maxDeliver the server would retire it silently instead, left + // on the work stream where nothing delivers or counts it. + maxDeliver: $this->maxDeliver + 1, filterSubject: $this->workSubject($queue), maxWaiting: $this->maxWaiting, maxAckPending: $this->maxAckPending, inactiveThreshold: $this->inactiveThreshold, + metadata: [self::METADATA_SPARE_DELIVERY => '1'], backoff: $this->backoff, )); - // Best-effort terminal dead-lettering for the crash-loop case: a worker that - // dies (never reject()s) is redelivered by AckWait until maxDeliver, after which - // JetStream stops delivering and emits this advisory. We drain it in receive() - // and move the stuck message to the dead stream. Caveat: core - // advisories are ephemeral, so a message that exhausts while no broker is - // subscribed stays as pending backlog (still visible) rather than dead-lettered. + // The fallback for the spare delivery above dying too: JetStream then stops + // delivering and emits this advisory, which receive() drains to move the + // message to the dead stream. Only a fallback, because core advisories are + // ephemeral: one emitted while no broker is subscribed is lost, and the + // message stays on the work stream reading as neither pending nor in flight. // The queue group is what keeps this to one dead-letter copy. A plain // subscription delivers the advisory to every worker process, and each // of them then publishes its own copy of the exhausted message onto the @@ -1313,6 +1343,7 @@ private function provision(Queue $queue, string $key): void queue: self::ADVISORY_GROUP, ); + $this->spareDelivery[$key] = $this->maxDeliver + 1; $this->provisioned[$key] = true; } @@ -1348,6 +1379,20 @@ private function adopt(Queue $queue, string $key): void $this->consumers[$key] = $this->adoptConsumer($queue, self::CONSUMER_WORK); + // The owner's spare delivery, not this broker's: it exists only if the owner + // provisioned one, which it marks in the consumer's metadata. A consumer from + // before the spare was introduced allows none, and nothing here may change that, + // so it is reported instead -- a message it exhausts while no broker is + // subscribed stays on the work stream until its owner reprovisions. A limit + // below 1 is unlimited: such a message never exhausts. + $config = $this->consumers[$key]->info()->config; + $allowed = $config->maxDeliver ?? -1; + $marked = isset($config->metadata[self::METADATA_SPARE_DELIVERY]); + $this->spareDelivery[$key] = $marked && $allowed >= 1 ? $allowed : null; + if (!$marked && $allowed >= 1) { + $this->report(new \RuntimeException('NATS consumer "' . self::CONSUMER_WORK . "\" on stream \"{$this->workStream($queue)}\" allows no delivery past max_deliver {$allowed}, so a message exhausted while no broker is subscribed is left on the work stream; reprovision it from the queue's owner.")); + } + // Same advisory subscription provision() takes: a core subscription carries no // configuration, and a broker consuming a pre-provisioned queue still owes its // exhausted messages a dead letter. diff --git a/packages/queue/tests/E2E/NatsBrokerTest.php b/packages/queue/tests/E2E/NatsBrokerTest.php index ad4f1586a24..ac3c9166f53 100644 --- a/packages/queue/tests/E2E/NatsBrokerTest.php +++ b/packages/queue/tests/E2E/NatsBrokerTest.php @@ -9,6 +9,7 @@ use Utopia\NATS\Connection; use Utopia\NATS\ConnectionOptions; use Utopia\NATS\Exception\ConnectionException; +use Utopia\NATS\JetStream\ConsumerConfig; use Utopia\NATS\JetStream\DiscardPolicy; use Utopia\NATS\JetStream\StorageType; use Utopia\NATS\Transport\TcpTransport; @@ -605,6 +606,34 @@ public function testCrashLoopedMessageIsTerminallyDeadLettered(): void $broker->close(); } + public function testAMessageExhaustedWhileNoBrokerListensStillReachesTheDeadStream(): void + { + // The max-deliveries advisory is core NATS: nobody subscribed, nobody gets it. + // A worker that dies holding a message's last delivery -- a rollout, a crash + // loop -- used to leave it on the work stream past maxDeliver, where nothing + // delivers it, getQueueSize() reads 0 and it never expires. Seen on staging: + // 110 edge and 35 webhook messages, stranded for over a day. + $url = getenv('NATS_URL') ?: 'nats://127.0.0.1:14225'; + $queue = new Queue('t_' . substr(md5(uniqid('', true)), 0, 8)); + + $dying = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); + $dying->publish($queue, ['poison' => true]); + $this->assertCount(1, $dying->receive($queue, 2)); + sleep(2); + $this->assertCount(1, $dying->receive($queue, 2)); // the last delivery, never acked + $dying->close(); + sleep(2); // past ackWait with no broker subscribed to anything + + $fresh = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); + $this->assertSame([], $fresh->receive($queue, 2), 'an exhausted message must not reach a handler'); + + $js = Connection::connect($url)->jetStream(); + $this->assertSame(0, $js->getStreamInfo('Q_' . strtoupper($queue->name))->state->messages, 'work stream holds nothing'); + $this->assertSame(1, $js->getStreamInfo('Q_' . strtoupper($queue->name) . '_DEAD')->state->messages, 'the message is on the dead stream'); + + $fresh->close(); + } + /** * getQueueSize() must be safe to call while another coroutine is blocked in * receive() on the SAME broker — the shape the Swoole worker runs, where the @@ -1549,6 +1578,68 @@ public function testRequireLeavesStreamAndConsumerConfigUntouched(): void $owner->close(); } + public function testARequireBrokerDeadLettersOnTheOwnersSpareDelivery(): void + { + // A Require broker writes no configuration, so the spare delivery it can use is + // the one the owner provisioned -- read from the server, not from its own knobs. + $url = getenv('NATS_URL') ?: 'nats://127.0.0.1:14225'; + $queue = new Queue('t_' . substr(md5(uniqid('', true)), 0, 8)); + + $owner = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); + $owner->publish($queue, ['poison' => true]); + $this->assertCount(1, $owner->receive($queue, 2)); + sleep(2); + $this->assertCount(1, $owner->receive($queue, 2)); + $owner->close(); + sleep(2); + + $adopter = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 9, provisioning: Provisioning::Require); + $this->assertSame([], $adopter->receive($queue, 2), 'an exhausted message must not reach a handler'); + + $js = Connection::connect($url)->jetStream(); + $this->assertSame(0, $js->getStreamInfo('Q_' . strtoupper($queue->name))->state->messages, 'work stream holds nothing'); + $this->assertSame(1, $js->getStreamInfo('Q_' . strtoupper($queue->name) . '_DEAD')->state->messages, 'the message is on the dead stream'); + + $adopter->close(); + } + + public function testARequireBrokerReportsAConsumerWithoutASpareDelivery(): void + { + // A consumer provisioned before the spare delivery existed, which a Require + // broker may not change: it says so rather than leaving strandings unexplained. + $url = getenv('NATS_URL') ?: 'nats://127.0.0.1:14225'; + $queue = new Queue('t_' . substr(md5(uniqid('', true)), 0, 8)); + $owner = new Nats(Connection::connect($url), maxDeliver: 2); + $owner->publish($queue, ['task' => 'a']); + $owner->close(); + + $js = Connection::connect($url)->jetStream(); + $stream = 'Q_' . strtoupper($queue->name); + $legacy = $js->getConsumer($stream, 'worker')->info(true)->config->toArray(); + // What a pre-spare broker provisioned: no extra delivery, and no marker for one. + $legacy['max_deliver'] = 2; + $this->assertIsArray($legacy['metadata']); + unset($legacy['metadata']['utopia_queue_spare_delivery']); + $js->updateConsumer($stream, ConsumerConfig::fromArray($legacy)); + + $reported = []; + $adopter = new Nats( + Connection::connect($url), + maxDeliver: 2, + onError: static function (\Throwable $error) use (&$reported): void { + $reported[] = $error->getMessage(); + }, + provisioning: Provisioning::Require, + ); + $adopter->receive($queue, 1); + + $this->assertCount(1, $reported); + $this->assertStringContainsString('allows no delivery past max_deliver 2', $reported[0]); + $this->assertSame(2, $js->getConsumer($stream, 'worker')->info(true)->config->maxDeliver, 'and leaves the consumer as it found it'); + + $adopter->close(); + } + // JetStream updates a consumer's num_pending asynchronously after the publish ack. private function pendingSettled(int $expected): int { From 452890d5606c6b169a2263bd350e2baf439d63c2 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:15:28 +0200 Subject: [PATCH 412/470] fix(queue): re-read the spare delivery before parking on it The work consumer is shared, so a broker with a larger maxDeliver can reprovision it after this one cached its spare. Parking on the cached threshold then took attempts the server still allowed. The consumer's config is re-read on that path only, before a message is parked. Tests: drop sleeps that a blocking receive() already covers, and assert that the missing spare is reported rather than the message wording. --- packages/queue/phpstan-baseline.neon | 2 +- packages/queue/src/Broker/Nats.php | 32 ++++++++++++----- packages/queue/tests/E2E/NatsBrokerTest.php | 39 ++++++++++++++++----- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/queue/phpstan-baseline.neon b/packages/queue/phpstan-baseline.neon index cf6fa3c54e8..f9b5e1de9a9 100644 --- a/packages/queue/phpstan-baseline.neon +++ b/packages/queue/phpstan-baseline.neon @@ -985,7 +985,7 @@ parameters: path: tests/E2E/NatsBrokerTest.php - - message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/E2E/NatsBrokerTest\.php\:892\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/E2E/NatsBrokerTest\.php\:893\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue count: 1 path: tests/E2E/NatsBrokerTest.php diff --git a/packages/queue/src/Broker/Nats.php b/packages/queue/src/Broker/Nats.php index 7d3a7257871..ffc72f3a1c1 100644 --- a/packages/queue/src/Broker/Nats.php +++ b/packages/queue/src/Broker/Nats.php @@ -743,9 +743,15 @@ private function pull(Queue $queue, int $timeout, int $max): array // died holding each one. Dead-lettered on this delivery rather than by the // advisory, which nobody receives while no broker is subscribed. if ($spare !== null && $jsMessage->metadata()->numDelivered >= $spare) { - $this->park($queue, $jsMessage, 'max deliveries exceeded'); - - continue; + // The consumer is shared: a broker with a larger maxDeliver may have + // reprovisioned it since this one cached its spare. Parking on a stale + // threshold would take attempts the server still allows, so re-read it. + $spare = $this->spareDelivery[$key] = $this->spareOf($this->consumers[$key]->info(true)->config); + if ($spare !== null && $jsMessage->metadata()->numDelivered >= $spare) { + $this->park($queue, $jsMessage, 'max deliveries exceeded'); + + continue; + } } try { @@ -1383,13 +1389,11 @@ private function adopt(Queue $queue, string $key): void // provisioned one, which it marks in the consumer's metadata. A consumer from // before the spare was introduced allows none, and nothing here may change that, // so it is reported instead -- a message it exhausts while no broker is - // subscribed stays on the work stream until its owner reprovisions. A limit - // below 1 is unlimited: such a message never exhausts. + // subscribed stays on the work stream until its owner reprovisions. $config = $this->consumers[$key]->info()->config; + $this->spareDelivery[$key] = $this->spareOf($config); $allowed = $config->maxDeliver ?? -1; - $marked = isset($config->metadata[self::METADATA_SPARE_DELIVERY]); - $this->spareDelivery[$key] = $marked && $allowed >= 1 ? $allowed : null; - if (!$marked && $allowed >= 1) { + if ($this->spareDelivery[$key] === null && $allowed >= 1) { $this->report(new \RuntimeException('NATS consumer "' . self::CONSUMER_WORK . "\" on stream \"{$this->workStream($queue)}\" allows no delivery past max_deliver {$allowed}, so a message exhausted while no broker is subscribed is left on the work stream; reprovision it from the queue's owner.")); } @@ -1404,6 +1408,18 @@ private function adopt(Queue $queue, string $key): void $this->provisioned[$key] = true; } + /** + * The delivery a work consumer's configuration spares for dead-lettering: its last, + * when the metadata marks one past maxDeliver; null when it spares none. A limit + * below 1 is unlimited, so nothing ever exhausts it. + */ + private function spareOf(ConsumerConfig $config): ?int + { + $allowed = $config->maxDeliver ?? -1; + + return isset($config->metadata[self::METADATA_SPARE_DELIVERY]) && $allowed >= 1 ? $allowed : null; + } + /** Resolve an existing durable consumer, refusing rather than creating it. */ private function adoptConsumer(Queue $queue, string $durable): NatsConsumer { diff --git a/packages/queue/tests/E2E/NatsBrokerTest.php b/packages/queue/tests/E2E/NatsBrokerTest.php index ac3c9166f53..aa2b02996cb 100644 --- a/packages/queue/tests/E2E/NatsBrokerTest.php +++ b/packages/queue/tests/E2E/NatsBrokerTest.php @@ -619,10 +619,11 @@ public function testAMessageExhaustedWhileNoBrokerListensStillReachesTheDeadStre $dying = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); $dying->publish($queue, ['poison' => true]); $this->assertCount(1, $dying->receive($queue, 2)); - sleep(2); - $this->assertCount(1, $dying->receive($queue, 2)); // the last delivery, never acked + $this->assertCount(1, $dying->receive($queue, 2)); // redelivered after ackWait: the last, never acked $dying->close(); - sleep(2); // past ackWait with no broker subscribed to anything + // Not a wait for an event: the gap itself is the scenario. The last delivery's + // ackWait has to lapse while nothing is subscribed, or the advisory is caught. + sleep(2); $fresh = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); $this->assertSame([], $fresh->receive($queue, 2), 'an exhausted message must not reach a handler'); @@ -1588,10 +1589,9 @@ public function testARequireBrokerDeadLettersOnTheOwnersSpareDelivery(): void $owner = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); $owner->publish($queue, ['poison' => true]); $this->assertCount(1, $owner->receive($queue, 2)); - sleep(2); $this->assertCount(1, $owner->receive($queue, 2)); $owner->close(); - sleep(2); + sleep(2); // the last delivery's ackWait lapses with nothing subscribed $adopter = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 9, provisioning: Provisioning::Require); $this->assertSame([], $adopter->receive($queue, 2), 'an exhausted message must not reach a handler'); @@ -1617,7 +1617,7 @@ public function testARequireBrokerReportsAConsumerWithoutASpareDelivery(): void $stream = 'Q_' . strtoupper($queue->name); $legacy = $js->getConsumer($stream, 'worker')->info(true)->config->toArray(); // What a pre-spare broker provisioned: no extra delivery, and no marker for one. - $legacy['max_deliver'] = 2; + $legacy['max_deliver'] = $maxDeliver = 2; $this->assertIsArray($legacy['metadata']); unset($legacy['metadata']['utopia_queue_spare_delivery']); $js->updateConsumer($stream, ConsumerConfig::fromArray($legacy)); @@ -1633,13 +1633,34 @@ public function testARequireBrokerReportsAConsumerWithoutASpareDelivery(): void ); $adopter->receive($queue, 1); - $this->assertCount(1, $reported); - $this->assertStringContainsString('allows no delivery past max_deliver 2', $reported[0]); - $this->assertSame(2, $js->getConsumer($stream, 'worker')->info(true)->config->maxDeliver, 'and leaves the consumer as it found it'); + $this->assertCount(1, $reported, 'the missing spare delivery is reported'); + $this->assertSame($maxDeliver, $js->getConsumer($stream, 'worker')->info(true)->config->maxDeliver, 'and the consumer left as it was found'); $adopter->close(); } + public function testASpareDeliveryRaisedByAnotherBrokerIsNotParkedEarly(): void + { + // The work consumer is shared. A broker that cached a spare delivery of 3 must + // not park the 3rd delivery once a broker with a larger budget has reprovisioned + // the consumer to allow more. + $url = getenv('NATS_URL') ?: 'nats://127.0.0.1:14225'; + $queue = new Queue('t_' . substr(md5(uniqid('', true)), 0, 8)); + + $small = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); + $small->publish($queue, ['task' => 'a']); + $this->assertCount(1, $small->receive($queue, 2)); + $this->assertCount(1, $small->receive($queue, 2)); + + $large = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 5); + $this->assertCount(1, $large->receive($queue, 2), 'the 3rd delivery, within the new budget'); + + $this->assertCount(1, $small->receive($queue, 2), 'the 4th delivery reaches a handler, not the dead stream'); + + $small->close(); + $large->close(); + } + // JetStream updates a consumer's num_pending asynchronously after the publish ack. private function pendingSettled(int $expected): int { From 972431b008329a8630fd3b897996a9e28d7a9955 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 28 Sep 2026 16:34:36 +0100 Subject: [PATCH 413/470] chore(abuse): mirror plumbing --- .../abuse/.github/ISSUE_TEMPLATE/bug.yaml | 83 - .../.github/ISSUE_TEMPLATE/documentation.yaml | 32 - .../abuse/.github/ISSUE_TEMPLATE/feature.yaml | 40 - packages/abuse/.github/workflows/bench.yml | 21 - .../.github/workflows/codeql-analysis.yml | 20 - packages/abuse/.github/workflows/linter.yml | 20 - packages/abuse/.github/workflows/mirror.yml | 17 + packages/abuse/.github/workflows/tests.yml | 34 - packages/abuse/.gitignore | 3 +- packages/abuse/Dockerfile.php-8.4 | 27 - packages/abuse/Dockerfile.php-8.5 | 27 - packages/abuse/README.md | 4 +- packages/abuse/composer.json | 22 +- packages/abuse/composer.lock | 5684 ----------------- packages/abuse/pint.json | 3 - 15 files changed, 35 insertions(+), 6002 deletions(-) delete mode 100644 packages/abuse/.github/ISSUE_TEMPLATE/bug.yaml delete mode 100644 packages/abuse/.github/ISSUE_TEMPLATE/documentation.yaml delete mode 100644 packages/abuse/.github/ISSUE_TEMPLATE/feature.yaml delete mode 100644 packages/abuse/.github/workflows/bench.yml delete mode 100644 packages/abuse/.github/workflows/codeql-analysis.yml delete mode 100644 packages/abuse/.github/workflows/linter.yml create mode 100644 packages/abuse/.github/workflows/mirror.yml delete mode 100644 packages/abuse/.github/workflows/tests.yml delete mode 100644 packages/abuse/Dockerfile.php-8.4 delete mode 100644 packages/abuse/Dockerfile.php-8.5 delete mode 100644 packages/abuse/composer.lock delete mode 100644 packages/abuse/pint.json diff --git a/packages/abuse/.github/ISSUE_TEMPLATE/bug.yaml b/packages/abuse/.github/ISSUE_TEMPLATE/bug.yaml deleted file mode 100644 index 147e920ba7c..00000000000 --- a/packages/abuse/.github/ISSUE_TEMPLATE/bug.yaml +++ /dev/null @@ -1,83 +0,0 @@ -name: "🐛 Bug Report" -description: "Submit a bug report to help us improve" -title: "🐛 Bug Report: " -labels: [bug] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out our bug report form 🙏 - - type: textarea - id: steps-to-reproduce - validations: - required: true - attributes: - label: "👟 Reproduction steps" - description: "How do you trigger this bug? Please walk us through it step by step." - placeholder: "When I ..." - - type: textarea - id: expected-behavior - validations: - required: true - attributes: - label: "👍 Expected behavior" - description: "What did you think would happen?" - placeholder: "It should ..." - - type: textarea - id: actual-behavior - validations: - required: true - attributes: - label: "👎 Actual Behavior" - description: "What did actually happen? Add screenshots, if applicable." - placeholder: "It actually ..." - - type: dropdown - id: utopia-version - attributes: - label: "🎲 Utopia Abuse version" - description: "What version of Utopia Abuse are you running?" - options: - - Version 0.6.x - - Version 0.5.x - - Version 0.4.x - - Version 0.3.x - - Version 0.2.x - - Version 0.1.x - - Different version (specify in environment) - validations: - required: true - - type: dropdown - id: operating-system - attributes: - label: "💻 Operating system" - description: "What OS is your server / device running on?" - options: - - Linux - - MacOS - - Windows - - Something else - validations: - required: true - - type: textarea - id: enviromnemt - validations: - required: false - attributes: - label: "🧱 Your Environment" - description: "Is your environment customized in any way?" - placeholder: "I use Cloudflare for ..." - - type: checkboxes - id: no-duplicate-issues - attributes: - label: "👀 Have you spent some time to check if this issue has been raised before?" - description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" - options: - - label: "I checked and didn't find similar issue" - required: true - - type: checkboxes - id: read-code-of-conduct - attributes: - label: "🏢 Have you read the Code of Conduct?" - options: - - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" - required: true diff --git a/packages/abuse/.github/ISSUE_TEMPLATE/documentation.yaml b/packages/abuse/.github/ISSUE_TEMPLATE/documentation.yaml deleted file mode 100644 index b6189b09760..00000000000 --- a/packages/abuse/.github/ISSUE_TEMPLATE/documentation.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: "📚 Documentation" -description: "Report an issue related to documentation" -title: "📚 Documentation: " -labels: [documentation] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to make our documentation better 🙏 - - type: textarea - id: issue-description - validations: - required: true - attributes: - label: "💭 Description" - description: "A clear and concise description of what the issue is." - placeholder: "Documentation should not ..." - - type: checkboxes - id: no-duplicate-issues - attributes: - label: "👀 Have you spent some time to check if this issue has been raised before?" - description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" - options: - - label: "I checked and didn't find similar issue" - required: true - - type: checkboxes - id: read-code-of-conduct - attributes: - label: "🏢 Have you read the Code of Conduct?" - options: - - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" - required: true diff --git a/packages/abuse/.github/ISSUE_TEMPLATE/feature.yaml b/packages/abuse/.github/ISSUE_TEMPLATE/feature.yaml deleted file mode 100644 index 630af44eb44..00000000000 --- a/packages/abuse/.github/ISSUE_TEMPLATE/feature.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: 🚀 Feature -description: "Submit a proposal for a new feature" -title: "🚀 Feature: " -labels: [feature] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out our feature request form 🙏 - - type: textarea - id: feature-description - validations: - required: true - attributes: - label: "🔖 Feature description" - description: "A clear and concise description of what the feature is." - placeholder: "You should add ..." - - type: textarea - id: pitch - validations: - required: true - attributes: - label: "🎤 Pitch" - description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable." - placeholder: "In my use-case, ..." - - type: checkboxes - id: no-duplicate-issues - attributes: - label: "👀 Have you spent some time to check if this issue has been raised before?" - description: "Have you Googled for a similar issue or checked our older issues for a similar bug?" - options: - - label: "I checked and didn't find similar issue" - required: true - - type: checkboxes - id: read-code-of-conduct - attributes: - label: "🏢 Have you read the Code of Conduct?" - options: - - label: "I have read the [Code of Conduct](https://github.com/appwrite/appwrite/blob/HEAD/CODE_OF_CONDUCT.md)" - required: true diff --git a/packages/abuse/.github/workflows/bench.yml b/packages/abuse/.github/workflows/bench.yml deleted file mode 100644 index 153ed8a9b5a..00000000000 --- a/packages/abuse/.github/workflows/bench.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: "Benchmarks" - -on: [pull_request] -jobs: - lint: - name: Benchmarks - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Build - run: | - docker compose build - docker compose up -d - sleep 10 - - - name: Run benchmark - run: | - docker compose exec tests vendor/bin/phpbench run --report=aggregate --progress=plain \ No newline at end of file diff --git a/packages/abuse/.github/workflows/codeql-analysis.yml b/packages/abuse/.github/workflows/codeql-analysis.yml deleted file mode 100644 index e5100dba6db..00000000000 --- a/packages/abuse/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "CodeQL" - -on: [ pull_request ] -jobs: - lint: - name: CodeQL - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - run: git checkout HEAD^2 - - - name: Run CodeQL - run: | - docker run --rm -v $PWD:/app composer sh -c \ - "composer install --profile --ignore-platform-reqs && composer check" \ No newline at end of file diff --git a/packages/abuse/.github/workflows/linter.yml b/packages/abuse/.github/workflows/linter.yml deleted file mode 100644 index 15853dbe01b..00000000000 --- a/packages/abuse/.github/workflows/linter.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "Linter" - -on: [ pull_request ] -jobs: - lint: - name: Linter - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - run: git checkout HEAD^2 - - - name: Run Linter - run: | - docker run --rm -v $PWD:/app composer sh -c \ - "composer install --profile --ignore-platform-reqs && composer lint" diff --git a/packages/abuse/.github/workflows/mirror.yml b/packages/abuse/.github/workflows/mirror.yml new file mode 100644 index 00000000000..6b646ca6610 --- /dev/null +++ b/packages/abuse/.github/workflows/mirror.yml @@ -0,0 +1,17 @@ +name: Mirror + +on: + pull_request_target: + types: [opened] + issues: + types: [opened] + +permissions: + issues: write + pull-requests: write + +jobs: + redirect: + uses: appwrite/appwrite/.github/workflows/mirror-redirect.yml@main + with: + package: abuse diff --git a/packages/abuse/.github/workflows/tests.yml b/packages/abuse/.github/workflows/tests.yml deleted file mode 100644 index fec77ce3b8d..00000000000 --- a/packages/abuse/.github/workflows/tests.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "Tests" - -on: [ pull_request ] -jobs: - lint: - name: Tests - runs-on: ubuntu-latest - strategy: - matrix: - php-versions: ['8.4', '8.5'] - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - run: git checkout HEAD^2 - - - name: Generate .env file - run: | - echo "APPWRITE_ENDPOINT=${{ secrets.APPWRITE_ENDPOINT }}" > .env - echo "APPWRITE_PROJECT_ID=${{ secrets.APPWRITE_PROJECT_ID }}" >> .env - echo "APPWRITE_API_KEY=${{ secrets.APPWRITE_API_KEY }}" >> .env - - - name: Build - run: | - export PHP_VERSION=${{ matrix.php-versions }} - docker compose build - docker compose up -d - sleep 10 - - - name: Run Tests - run: docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml diff --git a/packages/abuse/.gitignore b/packages/abuse/.gitignore index 0ec0248901f..cd3ccb4757f 100755 --- a/packages/abuse/.gitignore +++ b/packages/abuse/.gitignore @@ -1,3 +1,4 @@ /vendor/ /.idea/ -.env \ No newline at end of file +.env +composer.lock diff --git a/packages/abuse/Dockerfile.php-8.4 b/packages/abuse/Dockerfile.php-8.4 deleted file mode 100644 index 8e929fbbe20..00000000000 --- a/packages/abuse/Dockerfile.php-8.4 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY composer.lock /src/ -COPY composer.json /src/ - -RUN composer install --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.4-1.0.0 as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./phpbench.json /code/phpbench.json - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/packages/abuse/Dockerfile.php-8.5 b/packages/abuse/Dockerfile.php-8.5 deleted file mode 100644 index 19e69648beb..00000000000 --- a/packages/abuse/Dockerfile.php-8.5 +++ /dev/null @@ -1,27 +0,0 @@ -FROM composer:2.0 as step0 - -WORKDIR /src/ - -COPY composer.lock /src/ -COPY composer.json /src/ - -RUN composer install --ignore-platform-reqs --optimize-autoloader \ - --no-plugins --no-scripts --prefer-dist - -FROM appwrite/utopia-base:php-8.5-2.1.0 as final - -LABEL maintainer="team@appwrite.io" - -RUN docker-php-ext-install pdo_mysql - -WORKDIR /code - -COPY --from=step0 /src/vendor /code/vendor - -# Add Source Code -COPY ./tests /code/tests -COPY ./src /code/src -COPY ./phpunit.xml /code/phpunit.xml -COPY ./phpbench.json /code/phpbench.json - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/packages/abuse/README.md b/packages/abuse/README.md index 401640485eb..6a2f4f8190e 100644 --- a/packages/abuse/README.md +++ b/packages/abuse/README.md @@ -1,6 +1,8 @@ # Utopia Abuse -[![Build Status](https://travis-ci.org/utopia-php/abuse.svg?branch=master)](https://travis-ci.com/utopia-php/abuse) +> [!IMPORTANT] +> This repository is a read-only mirror of [`packages/abuse`](https://github.com/appwrite/appwrite/tree/main/packages/abuse) in [appwrite/appwrite](https://github.com/appwrite/appwrite). Development happens there — please open issues and pull requests against appwrite/appwrite. + ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/abuse.svg) [![Discord](https://img.shields.io/discord/564160730845151244)](https://appwrite.io/discord) diff --git a/packages/abuse/composer.json b/packages/abuse/composer.json index a79d7711e95..be939af515f 100755 --- a/packages/abuse/composer.json +++ b/packages/abuse/composer.json @@ -2,19 +2,26 @@ "name": "utopia-php/abuse", "description": "A simple abuse library to manage application usage limits", "type": "library", - "keywords": ["php","framework", "upf", "utopia", "abuse"], + "keywords": [ + "php", + "framework", + "upf", + "utopia", + "abuse" + ], "license": "MIT", "minimum-stability": "stable", "autoload": { - "psr-4": {"Utopia\\Abuse\\": "src/Abuse"} + "psr-4": { + "Utopia\\Abuse\\": "src/Abuse" + } }, "autoload-dev": { - "psr-4": {"Utopia\\Tests\\": "tests/Abuse"} + "psr-4": { + "Utopia\\Tests\\": "tests/Abuse" + } }, "scripts": { - "check": "./vendor/bin/phpstan analyse --level max --memory-limit=2G src tests", - "lint": "./vendor/bin/pint --test", - "format": "./vendor/bin/pint", "bench": "vendor/bin/phpbench run --report=aggregate" }, "require": { @@ -27,9 +34,6 @@ "appwrite/appwrite": "^27.1" }, "require-dev": { - "phpunit/phpunit": "9.*", - "phpstan/phpstan": "1.*", - "laravel/pint": "1.*", "phpbench/phpbench": "1.*" }, "config": { diff --git a/packages/abuse/composer.lock b/packages/abuse/composer.lock deleted file mode 100644 index a69036956d3..00000000000 --- a/packages/abuse/composer.lock +++ /dev/null @@ -1,5684 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "bfae95b93046381a457db8b4f33d5b35", - "packages": [ - { - "name": "adhocore/jwt", - "version": "v1.1.4", - "source": { - "type": "git", - "url": "https://github.com/adhocore/php-jwt.git", - "reference": "b7055ae9024a6627f19f9873b5db19b0f1dcc4e7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/adhocore/php-jwt/zipball/b7055ae9024a6627f19f9873b5db19b0f1dcc4e7", - "reference": "b7055ae9024a6627f19f9873b5db19b0f1dcc4e7", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ahc\\Jwt\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jitendra Adhikari", - "email": "jiten.adhikary@gmail.com" - } - ], - "description": "Ultra lightweight JSON web token (JWT) library for PHP5.5+.", - "keywords": [ - "auth", - "json-web-token", - "jwt", - "jwt-auth", - "jwt-php", - "token" - ], - "support": { - "issues": "https://github.com/adhocore/php-jwt/issues", - "source": "https://github.com/adhocore/php-jwt/tree/v1.1.4" - }, - "funding": [ - { - "url": "https://paypal.me/ji10", - "type": "custom" - }, - { - "url": "https://github.com/adhocore", - "type": "github" - } - ], - "time": "2026-05-30T07:58:15+00:00" - }, - { - "name": "appwrite/appwrite", - "version": "27.1.0", - "source": { - "type": "git", - "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "24685bc34928c0e5479d4977dc47ea4aa721f025" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/24685bc34928c0e5479d4977dc47ea4aa721f025", - "reference": "24685bc34928c0e5479d4977dc47ea4aa721f025", - "shasum": "" - }, - "require": { - "adhocore/jwt": "^1.1", - "ext-curl": "*", - "ext-json": "*", - "php": ">=8.2.0" - }, - "require-dev": { - "mockery/mockery": "1.6.12", - "phpunit/phpunit": "^12" - }, - "type": "library", - "autoload": { - "psr-4": { - "Appwrite\\": "src/Appwrite" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API", - "support": { - "email": "team@appwrite.io", - "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/27.1.0", - "url": "https://appwrite.io/support" - }, - "time": "2026-07-24T06:10:52+00:00" - }, - { - "name": "brick/math", - "version": "0.18.0", - "source": { - "type": "git", - "url": "https://github.com/brick/math.git", - "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", - "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", - "shasum": "" - }, - "require": { - "php": "^8.2" - }, - "require-dev": { - "phpstan/phpstan": "2.1.22", - "phpunit/phpunit": "^11.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Brick\\Math\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Arbitrary-precision arithmetic library", - "keywords": [ - "Arbitrary-precision", - "BigInteger", - "BigRational", - "arithmetic", - "bigdecimal", - "bignum", - "bignumber", - "brick", - "decimal", - "integer", - "math", - "mathematics", - "rational" - ], - "support": { - "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.18.0" - }, - "funding": [ - { - "url": "https://github.com/BenMorel", - "type": "github" - } - ], - "time": "2026-06-14T18:21:03+00:00" - }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, - { - "name": "google/protobuf", - "version": "v4.33.6", - "source": { - "type": "git", - "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "84b008c23915ed94536737eae46f41ba3bccfe67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/84b008c23915ed94536737eae46f41ba3bccfe67", - "reference": "84b008c23915ed94536737eae46f41ba3bccfe67", - "shasum": "" - }, - "require": { - "php": ">=8.1.0" - }, - "require-dev": { - "phpunit/phpunit": ">=10.5.62 <11.0.0" - }, - "suggest": { - "ext-bcmath": "Need to support JSON deserialization" - }, - "type": "library", - "autoload": { - "psr-4": { - "Google\\Protobuf\\": "src/Google/Protobuf", - "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "proto library for PHP", - "homepage": "https://developers.google.com/protocol-buffers/", - "keywords": [ - "proto" - ], - "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.6" - }, - "time": "2026-03-18T17:32:05+00:00" - }, - { - "name": "mongodb/mongodb", - "version": "2.1.2", - "source": { - "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/0a2472ba9cbb932f7e43a8770aedb2fc30612a67", - "reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0", - "ext-mongodb": "^2.1", - "php": "^8.1", - "psr/log": "^1.1.4|^2|^3", - "symfony/polyfill-php85": "^1.32" - }, - "replace": { - "mongodb/builder": "*" - }, - "require-dev": { - "doctrine/coding-standard": "^12.0", - "phpunit/phpunit": "^10.5.35", - "rector/rector": "^2.1.4", - "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "6.5.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "MongoDB\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" - }, - { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" - }, - { - "name": "Jérôme Tamarelle", - "email": "jerome.tamarelle@mongodb.com" - } - ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", - "keywords": [ - "database", - "driver", - "mongodb", - "persistence" - ], - "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.2" - }, - "time": "2025-10-06T12:12:40+00:00" - }, - { - "name": "nyholm/psr7", - "version": "1.8.2", - "source": { - "type": "git", - "url": "https://github.com/Nyholm/psr7.git", - "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", - "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0" - }, - "provide": { - "php-http/message-factory-implementation": "1.0", - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "http-interop/http-factory-tests": "^0.9", - "php-http/message-factory": "^1.0", - "php-http/psr7-integration-tests": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "symfony/error-handler": "^4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Nyholm\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - }, - { - "name": "Martijn van der Ven", - "email": "martijn@vanderven.se" - } - ], - "description": "A fast PHP7 implementation of PSR-7", - "homepage": "https://tnyholm.se", - "keywords": [ - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.2" - }, - "funding": [ - { - "url": "https://github.com/Zegnat", - "type": "github" - }, - { - "url": "https://github.com/nyholm", - "type": "github" - } - ], - "time": "2024-09-09T07:06:30+00:00" - }, - { - "name": "nyholm/psr7-server", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/Nyholm/psr7-server.git", - "reference": "4335801d851f554ca43fa6e7d2602141538854dc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", - "reference": "4335801d851f554ca43fa6e7d2602141538854dc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "require-dev": { - "nyholm/nsa": "^1.1", - "nyholm/psr7": "^1.3", - "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Nyholm\\Psr7Server\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com" - }, - { - "name": "Martijn van der Ven", - "email": "martijn@vanderven.se" - } - ], - "description": "Helper classes to handle PSR-7 server requests", - "homepage": "http://tnyholm.se", - "keywords": [ - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/Nyholm/psr7-server/issues", - "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" - }, - "funding": [ - { - "url": "https://github.com/Zegnat", - "type": "github" - }, - { - "url": "https://github.com/nyholm", - "type": "github" - } - ], - "time": "2023-11-08T09:30:43+00:00" - }, - { - "name": "open-telemetry/api", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/api.git", - "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/6f8d237ce2c304ca85f31970f788e7f074d147be", - "reference": "6f8d237ce2c304ca85f31970f788e7f074d147be", - "shasum": "" - }, - "require": { - "open-telemetry/context": "^1.4", - "php": "^8.1", - "psr/log": "^1.1|^2.0|^3.0", - "symfony/polyfill-php82": "^1.26" - }, - "conflict": { - "open-telemetry/sdk": "<=1.11" - }, - "type": "library", - "extra": { - "spi": { - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" - ] - }, - "branch-alias": { - "dev-main": "1.8.x-dev" - } - }, - "autoload": { - "files": [ - "Trace/functions.php" - ], - "psr-4": { - "OpenTelemetry\\API\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "API for OpenTelemetry PHP.", - "keywords": [ - "Metrics", - "api", - "apm", - "logging", - "opentelemetry", - "otel", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2026-02-25T13:24:05+00:00" - }, - { - "name": "open-telemetry/context", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/context.git", - "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/context/zipball/3c414b246e0dabb7d6145404e6a5e4536ca18d07", - "reference": "3c414b246e0dabb7d6145404e6a5e4536ca18d07", - "shasum": "" - }, - "require": { - "php": "^8.1", - "symfony/polyfill-php82": "^1.26" - }, - "suggest": { - "ext-ffi": "To allow context switching in Fibers" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - } - }, - "autoload": { - "files": [ - "fiber/initialize_fiber_handler.php" - ], - "psr-4": { - "OpenTelemetry\\Context\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "Context implementation for OpenTelemetry PHP.", - "keywords": [ - "Context", - "opentelemetry", - "otel" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2025-10-19T06:44:33+00:00" - }, - { - "name": "open-telemetry/exporter-otlp", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/283a0d66522f2adc6d8d7debfd7686be91c282be", - "reference": "283a0d66522f2adc6d8d7debfd7686be91c282be", - "shasum": "" - }, - "require": { - "open-telemetry/api": "^1.0", - "open-telemetry/gen-otlp-protobuf": "^1.1", - "open-telemetry/sdk": "^1.0", - "php": "^8.1", - "php-http/discovery": "^1.14" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0.x-dev" - } - }, - "autoload": { - "files": [ - "_register.php" - ], - "psr-4": { - "OpenTelemetry\\Contrib\\Otlp\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "OTLP exporter for OpenTelemetry.", - "keywords": [ - "Metrics", - "exporter", - "gRPC", - "http", - "opentelemetry", - "otel", - "otlp", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2026-02-05T09:44:52+00:00" - }, - { - "name": "open-telemetry/gen-otlp-protobuf", - "version": "1.9.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/gen-otlp-protobuf.git", - "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/gen-otlp-protobuf/zipball/a229cf161d42001d64c8f21e8f678581fe1c66b9", - "reference": "a229cf161d42001d64c8f21e8f678581fe1c66b9", - "shasum": "" - }, - "require": { - "google/protobuf": "^3.22 || ^4.0", - "php": "^8.0" - }, - "suggest": { - "ext-protobuf": "For better performance, when dealing with the protobuf format" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Opentelemetry\\Proto\\": "Opentelemetry/Proto/", - "GPBMetadata\\Opentelemetry\\": "GPBMetadata/Opentelemetry/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "PHP protobuf files for communication with OpenTelemetry OTLP collectors/servers.", - "keywords": [ - "Metrics", - "apm", - "gRPC", - "logging", - "opentelemetry", - "otel", - "otlp", - "protobuf", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2025-10-19T06:44:33+00:00" - }, - { - "name": "open-telemetry/sdk", - "version": "1.14.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/6e3d0ce93e76555dd5e2f1d19443ff45b990e410", - "reference": "6e3d0ce93e76555dd5e2f1d19443ff45b990e410", - "shasum": "" - }, - "require": { - "ext-json": "*", - "nyholm/psr7-server": "^1.1", - "open-telemetry/api": "^1.8", - "open-telemetry/context": "^1.4", - "open-telemetry/sem-conv": "^1.0", - "php": "^8.1", - "php-http/discovery": "^1.14", - "psr/http-client": "^1.0", - "psr/http-client-implementation": "^1.0", - "psr/http-factory-implementation": "^1.0", - "psr/http-message": "^1.0.1|^2.0", - "psr/log": "^1.1|^2.0|^3.0", - "ramsey/uuid": "^3.0 || ^4.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php82": "^1.26", - "tbachert/spi": "^1.0.5" - }, - "suggest": { - "ext-gmp": "To support unlimited number of synchronous metric readers", - "ext-mbstring": "To increase performance of string operations", - "open-telemetry/sdk-configuration": "File-based OpenTelemetry SDK configuration" - }, - "type": "library", - "extra": { - "spi": { - "OpenTelemetry\\API\\Configuration\\ConfigEnv\\EnvComponentLoader": [ - "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderHttpConfig", - "OpenTelemetry\\API\\Instrumentation\\Configuration\\General\\ConfigEnv\\EnvComponentLoaderPeerConfig" - ], - "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\ResolverInterface": [ - "OpenTelemetry\\SDK\\Common\\Configuration\\Resolver\\SdkConfigurationResolver" - ], - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\HookManagerInterface": [ - "OpenTelemetry\\API\\Instrumentation\\AutoInstrumentation\\ExtensionHookManager" - ] - }, - "branch-alias": { - "dev-main": "1.12.x-dev" - } - }, - "autoload": { - "files": [ - "Common/Util/functions.php", - "Logs/Exporter/_register.php", - "Metrics/MetricExporter/_register.php", - "Propagation/_register.php", - "Trace/SpanExporter/_register.php", - "Common/Dev/Compatibility/_load.php", - "_autoload.php" - ], - "psr-4": { - "OpenTelemetry\\SDK\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "SDK for OpenTelemetry PHP.", - "keywords": [ - "Metrics", - "apm", - "logging", - "opentelemetry", - "otel", - "sdk", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2026-03-21T11:50:01+00:00" - }, - { - "name": "open-telemetry/sem-conv", - "version": "1.38.0", - "source": { - "type": "git", - "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "e613bc640a407def4991b8a936a9b27edd9a3240" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/e613bc640a407def4991b8a936a9b27edd9a3240", - "reference": "e613bc640a407def4991b8a936a9b27edd9a3240", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "OpenTelemetry\\SemConv\\": "." - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "opentelemetry-php contributors", - "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" - } - ], - "description": "Semantic conventions for OpenTelemetry PHP.", - "keywords": [ - "Metrics", - "apm", - "logging", - "opentelemetry", - "otel", - "semantic conventions", - "semconv", - "tracing" - ], - "support": { - "chat": "https://app.slack.com/client/T08PSQ7BQ/C01NFPCV44V", - "docs": "https://opentelemetry.io/docs/languages/php", - "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", - "source": "https://github.com/open-telemetry/opentelemetry-php" - }, - "time": "2026-01-21T04:14:03+00:00" - }, - { - "name": "php-http/discovery", - "version": "1.20.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/discovery.git", - "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", - "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0|^2.0", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "nyholm/psr7": "<1.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "*", - "psr/http-factory-implementation": "*", - "psr/http-message-implementation": "*" - }, - "require-dev": { - "composer/composer": "^1.0.2|^2.0", - "graham-campbell/phpspec-skip-example-extension": "^5.0", - "php-http/httplug": "^1.0 || ^2.0", - "php-http/message-factory": "^1.0", - "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", - "sebastian/comparator": "^3.0.5 || ^4.0.8", - "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" - }, - "type": "composer-plugin", - "extra": { - "class": "Http\\Discovery\\Composer\\Plugin", - "plugin-optional": true - }, - "autoload": { - "psr-4": { - "Http\\Discovery\\": "src/" - }, - "exclude-from-classmap": [ - "src/Composer/Plugin.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", - "homepage": "http://php-http.org", - "keywords": [ - "adapter", - "client", - "discovery", - "factory", - "http", - "message", - "psr17", - "psr7" - ], - "support": { - "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.20.0" - }, - "time": "2024-10-02T11:20:13+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, - "time": "2024-04-15T12:06:14+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "time": "2023-04-04T09:54:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "ramsey/collection", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" - }, - "time": "2025-03-22T05:38:12+00:00" - }, - { - "name": "ramsey/uuid", - "version": "4.9.3", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", - "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", - "shasum": "" - }, - "require": { - "brick/math": ">=0.8.16 <=0.18", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.3" - }, - "time": "2026-06-18T03:57:49+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-13T15:52:40+00:00" - }, - { - "name": "symfony/http-client", - "version": "v7.4.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "e8a112b8415707265a7e614278136a9d92989a6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/e8a112b8415707265a7e614278136a9d92989a6a", - "reference": "e8a112b8415707265a7e614278136a9d92989a6a", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.4|^3.5.2", - "symfony/polyfill-php83": "^1.29", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "amphp/amp": "<2.5", - "amphp/socket": "<1.1", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/cache": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/rate-limiter": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-24T09:57:54+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", - "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-06T13:17:50+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.38.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-27T06:59:30+00:00" - }, - { - "name": "symfony/polyfill-php82", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php82.git", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", - "reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php82\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T12:45:58+00:00" - }, - { - "name": "symfony/polyfill-php83", - "version": "v1.38.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-27T06:51:48+00:00" - }, - { - "name": "symfony/polyfill-php85", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T02:25:22+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-03-28T09:44:51+00:00" - }, - { - "name": "tbachert/spi", - "version": "v1.0.5", - "source": { - "type": "git", - "url": "https://github.com/Nevay/spi.git", - "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Nevay/spi/zipball/e7078767866d0a9e0f91d3f9d42a832df5e39002", - "reference": "e7078767866d0a9e0f91d3f9d42a832df5e39002", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^2.0", - "composer/semver": "^1.0 || ^2.0 || ^3.0", - "php": "^8.1" - }, - "require-dev": { - "composer/composer": "^2.0", - "infection/infection": "^0.27.9", - "phpunit/phpunit": "^10.5", - "psalm/phar": "^5.18" - }, - "type": "composer-plugin", - "extra": { - "class": "Nevay\\SPI\\Composer\\Plugin", - "branch-alias": { - "dev-main": "1.0.x-dev" - }, - "plugin-optional": true - }, - "autoload": { - "psr-4": { - "Nevay\\SPI\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "description": "Service provider loading facility", - "keywords": [ - "service provider" - ], - "support": { - "issues": "https://github.com/Nevay/spi/issues", - "source": "https://github.com/Nevay/spi/tree/v1.0.5" - }, - "time": "2025-06-29T15:42:06+00:00" - }, - { - "name": "utopia-php/cache", - "version": "4.0.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/cache.git", - "reference": "2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e", - "reference": "2ab50440114a7699b8a017ee2ecd8bcd9d54ac5e", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-memcached": "*", - "ext-redis": "*", - "php": ">=8.3", - "utopia-php/circuit-breaker": "0.3.*", - "utopia-php/pools": "2.*", - "utopia-php/telemetry": "*" - }, - "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.12", - "phpunit/phpunit": "^9.3", - "swoole/ide-helper": "^6.0", - "vimeo/psalm": "4.13.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Cache\\": "src/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple cache library to manage application cache storing, loading and purging", - "keywords": [ - "cache", - "framework", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/4.0.0" - }, - "time": "2026-07-31T13:04:45+00:00" - }, - { - "name": "utopia-php/circuit-breaker", - "version": "0.3.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/circuit-breaker.git", - "reference": "db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/circuit-breaker/zipball/db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828", - "reference": "db5d77f6c99ebce2ee81bd8ed4ae8f41bd2b0828", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "laravel/pint": "^1.29", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^10.0", - "utopia-php/telemetry": "^0.4" - }, - "suggest": { - "ext-opentelemetry": "Required by utopia-php/telemetry when using OpenTelemetry metrics.", - "ext-protobuf": "Required by utopia-php/telemetry when using OpenTelemetry metrics.", - "ext-redis": "Required when using Utopia\\CircuitBreaker\\Adapter\\Redis with the phpredis extension.", - "ext-swoole": "Required when using Utopia\\CircuitBreaker\\Adapter\\SwooleTable.", - "utopia-php/telemetry": "Required when passing telemetry adapters or running the local telemetry demo." - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\CircuitBreaker\\": "src/CircuitBreaker" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Team Appwrite", - "email": "team@appwrite.io" - } - ], - "description": "Light & simple Circuit Breaker for PHP to prevent cascading failures in distributed systems.", - "keywords": [ - "circuit-breaker", - "fault-tolerance", - "framework", - "php", - "resilience", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/circuit-breaker/issues", - "source": "https://github.com/utopia-php/circuit-breaker/tree/0.3.1" - }, - "time": "2026-05-29T12:12:23+00:00" - }, - { - "name": "utopia-php/console", - "version": "0.1.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/console.git", - "reference": "d298e43960780e6d76e66de1228c75dc81220e3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/console/zipball/d298e43960780e6d76e66de1228c75dc81220e3e", - "reference": "d298e43960780e6d76e66de1228c75dc81220e3e", - "shasum": "" - }, - "require": { - "php": ">=8.0" - }, - "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.6", - "swoole/ide-helper": "4.8.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Console helpers for logging, prompting, and executing commands", - "keywords": [ - "cli", - "console", - "php", - "terminal", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/console/issues", - "source": "https://github.com/utopia-php/console/tree/0.1.1" - }, - "time": "2026-02-10T10:20:29+00:00" - }, - { - "name": "utopia-php/database", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/database.git", - "reference": "40185b100f92c402a189d6f4f0962c63bbe5726b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/40185b100f92c402a189d6f4f0962c63bbe5726b", - "reference": "40185b100f92c402a189d6f4f0962c63bbe5726b", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "ext-mongodb": "*", - "ext-pdo": "*", - "ext-redis": "*", - "php": ">=8.5", - "utopia-php/cache": "^4.0.0", - "utopia-php/console": "0.1.*", - "utopia-php/mongo": "1.*", - "utopia-php/pools": "2.*", - "utopia-php/validators": "0.3.*" - }, - "require-dev": { - "fakerphp/faker": "1.23.*", - "laravel/pint": "*", - "pcov/clobber": "2.*", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "9.*", - "rregeer/phpunit-coverage-check": "0.3.*", - "swoole/ide-helper": "5.1.3", - "utopia-php/cli": "0.22.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Database\\": "src/Database" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple library to manage application persistence using multiple database adapters", - "keywords": [ - "database", - "framework", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/7.0.0" - }, - "time": "2026-07-31T13:33:10+00:00" - }, - { - "name": "utopia-php/mongo", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/mongo.git", - "reference": "78818fd295f2829aaad5b74c9094e8c6f603550d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/mongo/zipball/78818fd295f2829aaad5b74c9094e8c6f603550d", - "reference": "78818fd295f2829aaad5b74c9094e8c6f603550d", - "shasum": "" - }, - "require": { - "ext-mongodb": "2.1.*", - "mongodb/mongodb": "2.1.*", - "php": ">=8.0", - "ramsey/uuid": "4.9.*" - }, - "require-dev": { - "fakerphp/faker": "1.*", - "laravel/pint": "*", - "phpstan/phpstan": "*", - "phpunit/phpunit": "9.*", - "swoole/ide-helper": "5.1.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Mongo\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eldad Fux", - "email": "eldad@appwrite.io" - }, - { - "name": "Wess", - "email": "wess@appwrite.io" - } - ], - "description": "A simple library to manage Mongo database", - "keywords": [ - "database", - "mongo", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/mongo/issues", - "source": "https://github.com/utopia-php/mongo/tree/1.4.0" - }, - "time": "2026-07-30T05:24:58+00:00" - }, - { - "name": "utopia-php/pools", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/pools.git", - "reference": "48905dac1b8f8050c2e07bdda32a018ca33982fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/48905dac1b8f8050c2e07bdda32a018ca33982fc", - "reference": "48905dac1b8f8050c2e07bdda32a018ca33982fc", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "utopia-php/telemetry": "^0.4" - }, - "require-dev": { - "swoole/ide-helper": "6.*" - }, - "suggest": { - "ext-mongodb": "Needed to support MongoDB database pools", - "ext-pdo": "Needed to support MariaDB, MySQL or SQLite database pools", - "ext-redis": "Needed to support Redis cache pools", - "ext-swoole": "Needed to support Swoole based pool adapter" - }, - "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/2.0.1" - }, - "time": "2026-07-31T12:19:18+00:00" - }, - { - "name": "utopia-php/telemetry", - "version": "0.4.5", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/telemetry.git", - "reference": "139943bffcd4f6dd8fb9ed247f946a1d151b006a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/telemetry/zipball/139943bffcd4f6dd8fb9ed247f946a1d151b006a", - "reference": "139943bffcd4f6dd8fb9ed247f946a1d151b006a", - "shasum": "" - }, - "require": { - "ext-protobuf": "*", - "nyholm/psr7": "1.*", - "open-telemetry/exporter-otlp": "1.*", - "open-telemetry/sdk": "1.*", - "php": ">=8.0", - "symfony/http-client": "7.*" - }, - "require-dev": { - "swoole/ide-helper": "6.*" - }, - "suggest": { - "ext-sockets": "Required for the Swoole transport implementation", - "ext-swoole": "Required for the Swoole transport implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Telemetry\\": "src/Telemetry" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A lite & fast telemetry library, with adapters for OpenTelemetry", - "keywords": [ - "framework", - "php", - "upf" - ], - "support": { - "issues": "https://github.com/utopia-php/telemetry/issues", - "source": "https://github.com/utopia-php/telemetry/tree/0.4.5" - }, - "time": "2026-07-08T11:07:25+00:00" - }, - { - "name": "utopia-php/validators", - "version": "0.3.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/validators.git", - "reference": "d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba", - "reference": "d9c2269ebd2596a09681ccd2fd133eeedfcdf9ba", - "shasum": "" - }, - "require": { - "php": ">=8.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A lightweight collection of reusable validators for Utopia projects", - "keywords": [ - "php", - "utopia", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.3.1" - }, - "time": "2026-07-14T11:55:48+00:00" - } - ], - "packages-dev": [ - { - "name": "doctrine/annotations", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.10.28", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6.4 || ^7", - "vimeo/psalm": "^4.30 || ^5.14" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.2" - }, - "abandoned": true, - "time": "2024-09-05T10:17:24+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", - "shasum": "" - }, - "require": { - "php": "^8.4" - }, - "require-dev": { - "doctrine/coding-standard": "^14", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5.58" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.1.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2026-01-05T06:47:08+00:00" - }, - { - "name": "doctrine/lexer", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.21" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2024-02-05T11:56:58+00:00" - }, - { - "name": "laravel/pint", - "version": "v1.29.3", - "source": { - "type": "git", - "url": "https://github.com/laravel/pint.git", - "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/da1d1111a6aa2e082d2a388b194afe1ba0a05d14", - "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-mbstring": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "php": "^8.2.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.95.8", - "illuminate/view": "^12.62.0", - "larastan/larastan": "^3.10.0", - "laravel-zero/framework": "^12.1.0", - "laravel/agent-detector": "^2.0.2", - "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.4.0", - "pestphp/pest": "^3.8.6" - }, - "bin": [ - "builds/pint" - ], - "type": "project", - "autoload": { - "psr-4": { - "App\\": "app/", - "Database\\Seeders\\": "database/seeders/", - "Database\\Factories\\": "database/factories/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nuno Maduro", - "email": "enunomaduro@gmail.com" - } - ], - "description": "An opinionated code formatter for PHP.", - "homepage": "https://laravel.com", - "keywords": [ - "dev", - "format", - "formatter", - "lint", - "linter", - "php" - ], - "support": { - "issues": "https://github.com/laravel/pint/issues", - "source": "https://github.com/laravel/pint" - }, - "time": "2026-06-16T15:34:04+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.13.4", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.7.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" - }, - "time": "2025-12-06T11:56:16+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpbench/container", - "version": "2.2.3", - "source": { - "type": "git", - "url": "https://github.com/phpbench/container.git", - "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196", - "reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196", - "shasum": "" - }, - "require": { - "psr/container": "^1.0|^2.0", - "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0" - }, - "require-dev": { - "php-cs-fixer/shim": "^3.89", - "phpstan/phpstan": "^0.12.52", - "phpunit/phpunit": "^8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpBench\\DependencyInjection\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Leech", - "email": "daniel@dantleech.com" - } - ], - "description": "Simple, configurable, service container.", - "support": { - "issues": "https://github.com/phpbench/container/issues", - "source": "https://github.com/phpbench/container/tree/2.2.3" - }, - "time": "2025-11-06T09:05:13+00:00" - }, - { - "name": "phpbench/phpbench", - "version": "1.7.0", - "source": { - "type": "git", - "url": "https://github.com/phpbench/phpbench.git", - "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpbench/phpbench/zipball/3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", - "reference": "3d13c0d5dcf8730a67b70fa7fb03b4556b5cc0fe", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^2.0", - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "ext-tokenizer": "*", - "php": "^8.2", - "phpbench/container": "^2.2", - "psr/log": "^1.1 || ^2.0 || ^3.0", - "seld/jsonlint": "^1.1", - "symfony/console": "^6.1 || ^7.0 || ^8.0", - "symfony/filesystem": "^6.1 || ^7.0 || ^8.0", - "symfony/finder": "^6.1 || ^7.0 || ^8.0", - "symfony/options-resolver": "^6.1 || ^7.0 || ^8.0", - "symfony/process": "^6.1 || ^7.0 || ^8.0", - "webmozart/glob": "^4.6" - }, - "require-dev": { - "dantleech/invoke": "^2.0", - "ergebnis/composer-normalize": "^2.39", - "jangregor/phpstan-prophecy": "^2.0", - "php-cs-fixer/shim": "^3.9", - "phpspec/prophecy": "^1.22", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^11.5", - "rector/rector": "^2.2", - "sebastian/exporter": "^6.3.2", - "symfony/error-handler": "^6.1 || ^7.0 || ^8.0", - "symfony/var-dumper": "^6.1 || ^7.0 || ^8.0" - }, - "suggest": { - "ext-xdebug": "For Xdebug profiling extension." - }, - "bin": [ - "bin/phpbench" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "files": [ - "lib/Report/Func/functions.php" - ], - "psr-4": { - "PhpBench\\": "lib/", - "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Leech", - "email": "daniel@dantleech.com" - } - ], - "description": "PHP Benchmarking Framework", - "keywords": [ - "benchmarking", - "optimization", - "performance", - "profiling", - "testing" - ], - "support": { - "issues": "https://github.com/phpbench/phpbench/issues", - "source": "https://github.com/phpbench/phpbench/tree/1.7.0" - }, - "funding": [ - { - "url": "https://github.com/dantleech", - "type": "github" - } - ], - "time": "2026-06-08T19:09:20+00:00" - }, - { - "name": "phpstan/phpstan", - "version": "1.12.33", - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1", - "reference": "37982d6fc7cbb746dda7773530cda557cdf119e1", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2026-02-28T20:30:03+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.32", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", - "theseer/tokenizer": "^1.2.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.6" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-08-22T04:23:01+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.6.34", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b36f02317466907a230d3aa1d34467041271ef4a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", - "reference": "b36f02317466907a230d3aa1d34467041271ef4a", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.5.0 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.10", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.8", - "sebastian/global-state": "^5.0.8", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.6-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2026-01-27T05:45:00+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:27:43+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-01-24T09:22:56+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:19:30+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:30:58+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:03:51+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", - "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2025-09-24T06:03:27+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", - "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", - "type": "tidelift" - } - ], - "time": "2025-08-10T07:10:35+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:20:34+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", - "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2025-08-10T06:57:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-14T16:00:52+00:00" - }, - { - "name": "sebastian/type", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:13:03+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "seld/jsonlint", - "version": "1.12.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", - "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2026-06-12T11:32:29+00:00" - }, - { - "name": "symfony/console", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", - "reference": "f5a856c6ecb56b3c21ed94a5b7bf940d857d110a", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php85": "^1.32", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4.6|^8.0.6" - }, - "conflict": { - "symfony/dependency-injection": "<8.1", - "symfony/event-dispatcher": "<8.1" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^8.1", - "symfony/event-dispatcher": "^8.1", - "symfony/filesystem": "^7.4|^8.0", - "symfony/http-foundation": "^7.4|^8.0", - "symfony/http-kernel": "^7.4|^8.0", - "symfony/lock": "^7.4|^8.0", - "symfony/messenger": "^7.4|^8.0", - "symfony/mime": "^7.4|^8.0", - "symfony/process": "^7.4|^8.0", - "symfony/stopwatch": "^7.4|^8.0", - "symfony/uid": "^7.4|^8.0", - "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/99aec13b82b4967ec5088222c4a3ecca955949c2", - "reference": "99aec13b82b4967ec5088222c4a3ecca955949c2", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/finder", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "58d2e767a66052c1487356f953445634a8194c64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/58d2e767a66052c1487356f953445634a8194c64", - "reference": "58d2e767a66052c1487356f953445634a8194c64", - "shasum": "" - }, - "require": { - "php": ">=8.4.1" - }, - "require-dev": { - "symfony/filesystem": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/88f9c561f678a02d54b897014049fa839e33ff82", - "reference": "88f9c561f678a02d54b897014049fa839e33ff82", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.37.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-04-10T16:19:22+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.38.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", - "reference": "e9247d281d694a5120554d9afaf54e070e88a603", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-26T05:58:03+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.38.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-25T13:48:31+00:00" - }, - { - "name": "symfony/process", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", - "shasum": "" - }, - "require": { - "php": ">=8.4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "symfony/string", - "version": "v8.1.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", - "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", - "shasum": "" - }, - "require": { - "php": ">=8.4.1", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-intl-grapheme": "^1.33", - "symfony/polyfill-intl-normalizer": "^1.0", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "symfony/translation-contracts": "<2.5" - }, - "require-dev": { - "symfony/emoji": "^7.4|^8.0", - "symfony/http-client": "^7.4|^8.0", - "symfony/intl": "^7.4|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^7.4|^8.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v8.1.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2026-05-29T05:06:50+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-11-17T20:03:58+00:00" - }, - { - "name": "webmozart/glob", - "version": "4.7.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/glob.git", - "reference": "8a2842112d6916e61e0e15e316465b611f3abc17" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/glob/zipball/8a2842112d6916e61e0e15e316465b611f3abc17", - "reference": "8a2842112d6916e61e0e15e316465b611f3abc17", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "symfony/filesystem": "^5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Glob\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "A PHP implementation of Ant's glob.", - "support": { - "issues": "https://github.com/webmozarts/glob/issues", - "source": "https://github.com/webmozarts/glob/tree/4.7.0" - }, - "time": "2024-03-07T20:33:40+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.4.1", - "ext-pdo": "*", - "ext-curl": "*", - "ext-redis": "*" - }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} diff --git a/packages/abuse/pint.json b/packages/abuse/pint.json deleted file mode 100644 index c7819336c36..00000000000 --- a/packages/abuse/pint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "preset": "psr12" -} \ No newline at end of file From 7086f0b7ea22ead9b5fe9e8786da9ecdfe9d1680 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 28 Sep 2026 16:50:24 +0100 Subject: [PATCH 414/470] refactor: load abuse from packages/ --- composer.json | 5 +- composer.lock | 55 +--- packages/abuse/.gitignore | 1 + packages/abuse/CONTRIBUTING.md | 101 ------- packages/abuse/README.md | 2 +- packages/abuse/composer.json | 6 +- packages/abuse/docker-compose.yml | 112 +++----- packages/abuse/phpbench.json | 2 +- packages/abuse/phpstan-baseline.neon | 265 ++++++++++++++++++ packages/abuse/phpstan.neon | 10 + packages/abuse/phpunit.xml | 28 +- packages/abuse/rector.php | 38 +++ packages/abuse/src/{Abuse => }/Abuse.php | 0 packages/abuse/src/{Abuse => }/Adapter.php | 0 .../src/{Abuse => }/Adapters/ReCaptcha.php | 0 .../{Abuse => }/Adapters/SlidingWindow.php | 0 .../Adapters/SlidingWindow/None.php | 0 .../Adapters/SlidingWindow/Redis.php | 0 .../Adapters/SlidingWindow/RedisBase.php | 0 .../Adapters/SlidingWindow/RedisCluster.php | 0 .../Adapters/SlidingWindow/RedisPool.php | 0 .../src/{Abuse => }/Adapters/TimeLimit.php | 0 .../Adapters/TimeLimit/Appwrite/TablesDB.php | 0 .../Adapters/TimeLimit/Database.php | 0 .../{Abuse => }/Adapters/TimeLimit/None.php | 0 .../{Abuse => }/Adapters/TimeLimit/Redis.php | 0 .../Adapters/TimeLimit/RedisCluster.php | 0 .../Adapters/TimeLimit/RedisPool.php | 0 .../src/{Abuse => }/Adapters/TokenBucket.php | 0 .../{Abuse => }/Adapters/TokenBucket/None.php | 0 .../Adapters/TokenBucket/Redis.php | 0 .../Adapters/TokenBucket/RedisBase.php | 0 .../Adapters/TokenBucket/RedisCluster.php | 0 .../Adapters/TokenBucket/RedisPool.php | 0 .../{Abuse => E2E}/Appwrite/TablesDBTest.php | 11 +- packages/abuse/tests/{Abuse => E2E}/Base.php | 2 +- .../tests/{Abuse => E2E}/DatabaseTest.php | 6 +- .../tests/{Abuse => E2E}/RedisClusterTest.php | 9 +- .../{Abuse => E2E}/RedisPoolClusterTest.php | 12 +- .../tests/{Abuse => E2E}/RedisPoolTest.php | 4 +- .../abuse/tests/{Abuse => E2E}/RedisTest.php | 4 +- packages/abuse/tests/E2E/Services.php | 18 ++ .../{Abuse => E2E}/SlidingWindow/Base.php | 2 +- .../SlidingWindow/RedisClusterTest.php | 10 +- .../SlidingWindow/RedisPoolTest.php | 5 +- .../SlidingWindow/RedisTest.php | 5 +- .../tests/{Abuse => E2E}/TokenBucket/Base.php | 2 +- .../TokenBucket/RedisClusterTest.php | 10 +- .../TokenBucket/RedisPoolTest.php | 5 +- .../{Abuse => E2E}/TokenBucket/RedisTest.php | 5 +- packages/abuse/tests/{Abuse => }/NoneTest.php | 2 +- .../tests/{Abuse/Bench => bench}/Base.php | 4 +- .../tests/{Abuse/Bench => bench}/Database.php | 7 +- .../tests/{Abuse/Bench => bench}/Redis.php | 5 +- .../{Abuse/Bench => bench}/RedisCluster.php | 5 +- rfc/monorepo.md | 1 + 56 files changed, 446 insertions(+), 313 deletions(-) delete mode 100644 packages/abuse/CONTRIBUTING.md mode change 100755 => 100644 packages/abuse/composer.json create mode 100644 packages/abuse/phpstan-baseline.neon create mode 100644 packages/abuse/phpstan.neon create mode 100644 packages/abuse/rector.php rename packages/abuse/src/{Abuse => }/Abuse.php (100%) rename packages/abuse/src/{Abuse => }/Adapter.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/ReCaptcha.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow/None.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow/Redis.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow/RedisBase.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow/RedisCluster.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/SlidingWindow/RedisPool.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/Appwrite/TablesDB.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/Database.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/None.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/Redis.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/RedisCluster.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TimeLimit/RedisPool.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket/None.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket/Redis.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket/RedisBase.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket/RedisCluster.php (100%) rename packages/abuse/src/{Abuse => }/Adapters/TokenBucket/RedisPool.php (100%) rename packages/abuse/tests/{Abuse => E2E}/Appwrite/TablesDBTest.php (94%) rename packages/abuse/tests/{Abuse => E2E}/Base.php (99%) rename packages/abuse/tests/{Abuse => E2E}/DatabaseTest.php (93%) rename packages/abuse/tests/{Abuse => E2E}/RedisClusterTest.php (80%) rename packages/abuse/tests/{Abuse => E2E}/RedisPoolClusterTest.php (89%) rename packages/abuse/tests/{Abuse => E2E}/RedisPoolTest.php (96%) rename packages/abuse/tests/{Abuse => E2E}/RedisTest.php (90%) create mode 100644 packages/abuse/tests/E2E/Services.php rename packages/abuse/tests/{Abuse => E2E}/SlidingWindow/Base.php (99%) rename packages/abuse/tests/{Abuse => E2E}/SlidingWindow/RedisClusterTest.php (78%) rename packages/abuse/tests/{Abuse => E2E}/SlidingWindow/RedisPoolTest.php (89%) rename packages/abuse/tests/{Abuse => E2E}/SlidingWindow/RedisTest.php (85%) rename packages/abuse/tests/{Abuse => E2E}/TokenBucket/Base.php (99%) rename packages/abuse/tests/{Abuse => E2E}/TokenBucket/RedisClusterTest.php (78%) rename packages/abuse/tests/{Abuse => E2E}/TokenBucket/RedisPoolTest.php (89%) rename packages/abuse/tests/{Abuse => E2E}/TokenBucket/RedisTest.php (85%) rename packages/abuse/tests/{Abuse => }/NoneTest.php (96%) rename packages/abuse/tests/{Abuse/Bench => bench}/Base.php (90%) rename packages/abuse/tests/{Abuse/Bench => bench}/Database.php (89%) rename packages/abuse/tests/{Abuse/Bench => bench}/Redis.php (75%) rename packages/abuse/tests/{Abuse/Bench => bench}/RedisCluster.php (72%) diff --git a/composer.json b/composer.json index 18edeeaafea..1513404f327 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "psr-4": { "Appwrite\\": "src/Appwrite", "Executor\\": "src/Executor", + "Utopia\\Abuse\\": "packages/abuse/src", "Utopia\\Agents\\": "packages/agents/src", "Utopia\\Auth\\": "packages/auth/src", "Utopia\\Balancer\\": "packages/balancer/src", @@ -75,6 +76,7 @@ "Tests\\E2E\\": "tests/e2e", "Tests\\Unit\\": "tests/unit", "Appwrite\\Tests\\": "tests/extensions", + "Utopia\\Abuse\\Tests\\": "packages/abuse/tests", "Utopia\\Agents\\Tests\\": "packages/agents/tests", "Utopia\\Auth\\Tests\\": "packages/auth/tests", "Utopia\\Balancer\\Tests\\": "packages/balancer/tests", @@ -138,7 +140,7 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.20.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "2.0.*", + "appwrite/appwrite": "^27.1", "utopia-php/audit": "^4.0", "utopia-php/config": "1.*", "utopia-php/database": "^7.3.11", @@ -190,6 +192,7 @@ } }, "replace": { + "utopia-php/abuse": "*", "utopia-php/agents": "*", "utopia-php/auth": "*", "utopia-php/balancer": "*", diff --git a/composer.lock b/composer.lock index 0e2f28316fa..982165b9e15 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": "b826809d11968b87eb0b03efe5c7cd85", + "content-hash": "248d936e25ae5be7031bc4e99191d800", "packages": [ { "name": "adhocore/jwt", @@ -2778,59 +2778,6 @@ }, "time": "2025-06-29T15:42:06+00:00" }, - { - "name": "utopia-php/abuse", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/abuse.git", - "reference": "821a1884c8067736e1a27d9fc70bdb2cc84d7146" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/821a1884c8067736e1a27d9fc70bdb2cc84d7146", - "reference": "821a1884c8067736e1a27d9fc70bdb2cc84d7146", - "shasum": "" - }, - "require": { - "appwrite/appwrite": "^27.1", - "ext-curl": "*", - "ext-pdo": "*", - "ext-redis": "*", - "php": ">=8.4.1", - "utopia-php/database": "^7.0.0", - "utopia-php/pools": "2.*" - }, - "require-dev": { - "laravel/pint": "1.*", - "phpbench/phpbench": "1.*", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "9.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Abuse\\": "src/Abuse" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple abuse library to manage application usage limits", - "keywords": [ - "Abuse", - "framework", - "php", - "upf", - "utopia" - ], - "support": { - "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/2.0.1" - }, - "time": "2026-08-13T11:44:26+00:00" - }, { "name": "utopia-php/audit", "version": "4.0.3", diff --git a/packages/abuse/.gitignore b/packages/abuse/.gitignore index cd3ccb4757f..6963341a694 100755 --- a/packages/abuse/.gitignore +++ b/packages/abuse/.gitignore @@ -1,4 +1,5 @@ /vendor/ /.idea/ .env +.phpunit.cache composer.lock diff --git a/packages/abuse/CONTRIBUTING.md b/packages/abuse/CONTRIBUTING.md deleted file mode 100644 index 29eb7940c0b..00000000000 --- a/packages/abuse/CONTRIBUTING.md +++ /dev/null @@ -1,101 +0,0 @@ -# Contributing - -We would ❤️ for you to contribute to Utopia-php and help make it better! We want contributing to Utopia-php to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, new docs as well as updates and tweaks, blog posts, workshops, and more. - -## How to Start? - -If you are worried or don’t know where to start, check out our next section explaining what kind of help we could use and where can you get involved. You can reach out with questions to [Eldad Fux (@eldadfux)](https://twitter.com/eldadfux) or anyone from the [Appwrite team on Discord](https://discord.gg/GSeTUeA). You can also submit an issue, and a maintainer can guide you! - -## Code of Conduct - -Help us keep Utopia-php open and inclusive. Please read and follow our [Code of Conduct](/CODE_OF_CONDUCT.md). - -## Submit a Pull Request 🚀 - -Branch naming convention is as following - -`TYPE-ISSUE_ID-DESCRIPTION` - -example: - -``` -doc-548-submit-a-pull-request-section-to-contribution-guide -``` - -When `TYPE` can be: - -- **feat** - is a new feature -- **doc** - documentation only changes -- **cicd** - changes related to CI/CD system -- **fix** - a bug fix -- **refactor** - code change that neither fixes a bug nor adds a feature - -**All PRs must include a commit message with the changes description!** - -For the initial start, fork the project and use git clone command to download the repository to your computer. A standard procedure for working on an issue would be to: - -1. `git pull`, before creating a new branch, pull the changes from upstream. Your master needs to be up to date. - -``` -$ git pull -``` - -2. Create new branch from `master` like: `doc-548-submit-a-pull-request-section-to-contribution-guide`
- -``` -$ git checkout -b [name_of_your_new_branch] -``` - -3. Work - commit - repeat ( be sure to be in your branch ) - -4. Push changes to GitHub - -``` -$ git push origin [name_of_your_new_branch] -``` - -5. Submit your changes for review - If you go to your repository on GitHub, you'll see a `Compare & pull request` button. Click on that button. -6. Start a Pull Request - Now submit the pull request and click on `Create pull request`. -7. Get a code review approval/reject -8. After approval, merge your PR -9. GitHub will automatically delete the branch after the merge is done. (they can still be restored). - -## Introducing New Features - -We would 💖 you to contribute to Utopia-php, but we would also like to make sure Utopia-php is as great as possible and loyal to its vision and mission statement 🙏. - -For us to find the right balance, please open an issue explaining your ideas before introducing a new pull request. - -This will allow the Utopia-php community to have sufficient discussion about the new feature value and how it fits in the product roadmap and vision. - -This is also important for the Utopia-php lead developers to be able to give technical input and different emphasis regarding the feature design and architecture. Some bigger features might need to go through our [RFC process](https://github.com/appwrite/rfc). - -## Other Ways to Help - -Pull requests are great, but there are many other areas where you can help Utopia-php. - -### Blogging & Speaking - -Blogging, speaking about, or creating tutorials about one of Utopia-php’s many features is great way to contribute and help our project grow. - -### Presenting at Meetups - -Presenting at meetups and conferences about your Utopia-php projects. Your unique challenges and successes in building things with Utopia-php can provide great speaking material. We’d love to review your talk abstract/CFP, so get in touch with us if you’d like some help! - -### Sending Feedbacks & Reporting Bugs - -Sending feedback is a great way for us to understand your different use cases of Utopia-php better. If you had any issues, bugs, or want to share about your experience, feel free to do so on our GitHub issues page or at our [Discord channel](https://discord.gg/GSeTUeA). - -### Submitting New Ideas - -If you think Utopia-php could use a new feature, please open an issue on our GitHub repository, stating as much information as you can think about your new idea and it's implications. We would also use this issue to gather more information, get more feedback from the community, and have a proper discussion about the new feature. - -### Improving Documentation - -Submitting documentation updates, enhancements, designs, or bug fixes. Spelling or grammar fixes will be very much appreciated. - -### Helping Someone - -Searching for Utopia-php, GitHub or StackOverflow and helping someone else who needs help. You can also help by teaching others how to contribute to Utopia-php's repo! diff --git a/packages/abuse/README.md b/packages/abuse/README.md index 6a2f4f8190e..efa3542663d 100644 --- a/packages/abuse/README.md +++ b/packages/abuse/README.md @@ -21,7 +21,7 @@ composer require utopia-php/abuse **Time Limit Abuse** The time limit abuse allow each key (action) to be performed [X] times in given time frame. -This adapter uses a MySQL / MariaDB to store usage attempts. Before using it create the table schema as documented in this repository (./data/schema.sql) +This adapter uses a MySQL / MariaDB to store usage attempts. Before using it, call `$adapter->setup()` once to create the collection it stores attempts in. ### Database adapter diff --git a/packages/abuse/composer.json b/packages/abuse/composer.json old mode 100755 new mode 100644 index be939af515f..cb78a60e73f --- a/packages/abuse/composer.json +++ b/packages/abuse/composer.json @@ -13,15 +13,17 @@ "minimum-stability": "stable", "autoload": { "psr-4": { - "Utopia\\Abuse\\": "src/Abuse" + "Utopia\\Abuse\\": "src/" } }, "autoload-dev": { "psr-4": { - "Utopia\\Tests\\": "tests/Abuse" + "Utopia\\Abuse\\Tests\\": "tests/" } }, "scripts": { + "test": "phpunit --testsuite unit", + "test:e2e": "phpunit --testsuite e2e", "bench": "vendor/bin/phpbench run --report=aggregate" }, "require": { diff --git a/packages/abuse/docker-compose.yml b/packages/abuse/docker-compose.yml index 8583a6cd892..9c6f60bc3b4 100644 --- a/packages/abuse/docker-compose.yml +++ b/packages/abuse/docker-compose.yml @@ -1,88 +1,40 @@ +name: utopia-abuse + services: mysql: image: mysql:8 - environment: - - MYSQL_ROOT_PASSWORD=password - networks: - - abuse + environment: + MYSQL_ROOT_PASSWORD: password ports: - - "9307:3306" + - "13308:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-ppassword"] + interval: 2s + timeout: 3s + retries: 60 redis: - image: redis:6.0-alpine - container_name: redis - networks: - - abuse - - tests: - build: - context: . - dockerfile: Dockerfile.php-${PHP_VERSION:-8.4} - networks: - - abuse - depends_on: - - redis - - mysql - environment: - - APPWRITE_ENDPOINT - - APPWRITE_PROJECT_ID - - APPWRITE_API_KEY - volumes: - - ./phpunit.xml:/code/phpunit.xml - - ./src:/code/src - - ./tests:/code/tests - - redis-insight: - image: redis/redisinsight:latest - restart: unless-stopped - networks: - - abuse - environment: - - REDIS_HOSTS=redis-cluster-0:6379 + image: redis:7-alpine + command: ["redis-server", "--save", "", "--appendonly", "no"] ports: - - "8081:5540" - volumes: - - redisinsight:/data - - redis-cluster-0: - image: bitnamilegacy/redis-cluster:7.4 + - "16386:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 1s + timeout: 1s + retries: 30 + + # The cluster advertises the addresses clients dial, so its published ports + # match the ones inside the container. + redis-cluster: + image: grokzen/redis-cluster:7.0.10 environment: - - ALLOW_EMPTY_PASSWORD=yes - - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 - - REDIS_CLUSTER_CREATOR=yes - - REDIS_CLUSTER_REPLICAS=0 - networks: - - abuse - depends_on: - - redis-cluster-1 - - redis-cluster-2 - - redis-cluster-3 - - redis-cluster-1: - image: bitnamilegacy/redis-cluster:7.4 - environment: - - ALLOW_EMPTY_PASSWORD=yes - - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 - networks: - - abuse - - redis-cluster-2: - image: bitnamilegacy/redis-cluster:7.4 - environment: - - ALLOW_EMPTY_PASSWORD=yes - - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 - networks: - - abuse - - redis-cluster-3: - image: bitnamilegacy/redis-cluster:7.4 - environment: - - ALLOW_EMPTY_PASSWORD=yes - - REDIS_NODES=redis-cluster-0 redis-cluster-1 redis-cluster-2 redis-cluster-3 - networks: - - abuse - -networks: - abuse: -volumes: - redisinsight: + IP: 127.0.0.1 + INITIAL_PORT: 17010 + ports: + - "17010-17015:17010-17015" + healthcheck: + test: ["CMD-SHELL", "redis-cli -p 17010 cluster info | grep -q cluster_state:ok"] + interval: 2s + timeout: 3s + retries: 60 diff --git a/packages/abuse/phpbench.json b/packages/abuse/phpbench.json index 19e8aa05945..f09975f9c4a 100644 --- a/packages/abuse/phpbench.json +++ b/packages/abuse/phpbench.json @@ -1,6 +1,6 @@ { "$schema": "vendor/phpbench/phpbench/phpbench.schema.json", "runner.bootstrap": "vendor/autoload.php", - "runner.path": "tests/Abuse/Bench", + "runner.path": "tests/bench", "runner.file_pattern": "*.php" } \ No newline at end of file diff --git a/packages/abuse/phpstan-baseline.neon b/packages/abuse/phpstan-baseline.neon new file mode 100644 index 00000000000..9546ee2864f --- /dev/null +++ b/packages/abuse/phpstan-baseline.neon @@ -0,0 +1,265 @@ +parameters: + ignoreErrors: + - + message: '#^Parameter \#3 \$value of function curl_setopt expects bool, int given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/ReCaptcha.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\SlidingWindow\\RedisCluster\:\:getLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/SlidingWindow/RedisCluster.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/SlidingWindow/RedisCluster.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects an array of values castable to string, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/SlidingWindow/RedisCluster.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/SlidingWindow/RedisCluster.php + + - + message: '#^Parameter \#2 \.\.\.\$arrays of function array_merge expects array, array\|true given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/SlidingWindow/RedisCluster.php + + - + message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Call to function is_array\(\) with array\ will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\SlidingWindow\\RedisPool\:\:getRedisClusterLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Only iterables can be unpacked, array\|true given in argument \#2\.$#' + identifier: argument.unpackNonIterable + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects an array of values castable to string, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/SlidingWindow/RedisPool.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\TimeLimit\\RedisCluster\:\:getLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/TimeLimit/RedisCluster.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/TimeLimit/RedisCluster.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects an array of values castable to string, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TimeLimit/RedisCluster.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TimeLimit/RedisCluster.php + + - + message: '#^Parameter \#2 \.\.\.\$arrays of function array_merge expects array, array\|true given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TimeLimit/RedisCluster.php + + - + message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Call to function is_array\(\) with array\ will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\TimeLimit\\RedisPool\:\:getRedisClusterLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Only iterables can be unpacked, array\|true given in argument \#2\.$#' + identifier: argument.unpackNonIterable + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects an array of values castable to string, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Parameter \#1 \$keys of function array_combine expects array\, list\ given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TimeLimit/RedisPool.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\TokenBucket\\RedisCluster\:\:getLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/TokenBucket/RedisCluster.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/TokenBucket/RedisCluster.php + + - + message: '#^Parameter \#1 \$key of method RedisCluster\:\:hGetAll\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TokenBucket/RedisCluster.php + + - + message: '#^Parameter \#2 \.\.\.\$arrays of function array_merge expects array, array\|true given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TokenBucket/RedisCluster.php + + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 1 + path: src/Adapters/TokenBucket/RedisCluster.php + + - + message: '#^Method Utopia\\Abuse\\Adapters\\TokenBucket\\RedisPool\:\:getRedisClusterLogs\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Adapters/TokenBucket/RedisPool.php + + - + message: '#^Only iterables can be unpacked, array\|true given in argument \#2\.$#' + identifier: argument.unpackNonIterable + count: 1 + path: src/Adapters/TokenBucket/RedisPool.php + + - + message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#' + identifier: argument.unresolvableType + count: 1 + path: src/Adapters/TokenBucket/RedisPool.php + + - + message: '#^Parameter \#1 \$key of method RedisCluster\:\:hGetAll\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Adapters/TokenBucket/RedisPool.php + + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 1 + path: src/Adapters/TokenBucket/RedisPool.php + + - + message: '#^Method Utopia\\Abuse\\Tests\\E2E\\Appwrite\\TablesDBTest\:\:columnsByKey\(\) should return array\\> but returns array\\>\.$#' + identifier: return.type + count: 1 + path: tests/E2E/Appwrite/TablesDBTest.php + + - + message: '#^Call to function is_int\(\) with int will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: tests/E2E/Base.php + + - + message: '#^Argument of an invalid type array\|true supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: tests/E2E/RedisPoolClusterTest.php + + - + message: '#^Instanceof between RedisCluster and RedisCluster will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: tests/E2E/RedisPoolClusterTest.php + + - + message: '#^Parameter \#1 \$key of method RedisCluster\:\:del\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/RedisPoolClusterTest.php + + - + message: '#^Instanceof between Redis and Redis will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: tests/E2E/RedisPoolTest.php + + - + message: '#^Instanceof between Redis and Redis will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: tests/E2E/SlidingWindow/RedisPoolTest.php + + - + message: '#^Call to function is_int\(\) with int will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: tests/E2E/TokenBucket/Base.php + + - + message: '#^Instanceof between Redis and Redis will always evaluate to true\.$#' + identifier: instanceof.alwaysTrue + count: 1 + path: tests/E2E/TokenBucket/RedisPoolTest.php diff --git a/packages/abuse/phpstan.neon b/packages/abuse/phpstan.neon new file mode 100644 index 00000000000..210b39af9ee --- /dev/null +++ b/packages/abuse/phpstan.neon @@ -0,0 +1,10 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - tests + excludePaths: + - tests/bench diff --git a/packages/abuse/phpunit.xml b/packages/abuse/phpunit.xml index ec39a7edeaf..4c54595ff57 100755 --- a/packages/abuse/phpunit.xml +++ b/packages/abuse/phpunit.xml @@ -1,16 +1,22 @@ - + + cacheDirectory=".phpunit.cache"> - - ./tests/ + + tests + tests/E2E + tests/bench + + + tests/E2E - \ No newline at end of file + + + src + + + diff --git a/packages/abuse/rector.php b/packages/abuse/rector.php new file mode 100644 index 00000000000..c9819ac6ced --- /dev/null +++ b/packages/abuse/rector.php @@ -0,0 +1,38 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets() + ->withPreparedSets( + typeDeclarations: true, + ) + // Absorbing moves code: keep src exactly as released. + ->withSkip([ + // Rector's PHP 8.4 printer drops the parentheses in `(new \DateTime())->` + // whenever it reprints these files. + __DIR__ . '/src/Adapters/TimeLimit/Appwrite/TablesDB.php', + __DIR__ . '/src/Adapters/TimeLimit/Database.php', + AddArrayFunctionClosureParamTypeRector::class => [__DIR__ . '/src'], + AddArrowFunctionReturnTypeRector::class => [__DIR__ . '/src'], + AddClosureVoidReturnTypeWhereNoReturnRector::class => [__DIR__ . '/src'], + ClassPropertyAssignToConstructorPromotionRector::class => [__DIR__ . '/src'], + ClosureReturnTypeRector::class => [__DIR__ . '/src'], + ClosureToArrowFunctionRector::class => [__DIR__ . '/src'], + NullCoalescingOperatorRector::class => [__DIR__ . '/src'], + RemoveUnusedVariableInCatchRector::class => [__DIR__ . '/src'], + ]); diff --git a/packages/abuse/src/Abuse/Abuse.php b/packages/abuse/src/Abuse.php similarity index 100% rename from packages/abuse/src/Abuse/Abuse.php rename to packages/abuse/src/Abuse.php diff --git a/packages/abuse/src/Abuse/Adapter.php b/packages/abuse/src/Adapter.php similarity index 100% rename from packages/abuse/src/Abuse/Adapter.php rename to packages/abuse/src/Adapter.php diff --git a/packages/abuse/src/Abuse/Adapters/ReCaptcha.php b/packages/abuse/src/Adapters/ReCaptcha.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/ReCaptcha.php rename to packages/abuse/src/Adapters/ReCaptcha.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow.php b/packages/abuse/src/Adapters/SlidingWindow.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow.php rename to packages/abuse/src/Adapters/SlidingWindow.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow/None.php b/packages/abuse/src/Adapters/SlidingWindow/None.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow/None.php rename to packages/abuse/src/Adapters/SlidingWindow/None.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow/Redis.php b/packages/abuse/src/Adapters/SlidingWindow/Redis.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow/Redis.php rename to packages/abuse/src/Adapters/SlidingWindow/Redis.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisBase.php b/packages/abuse/src/Adapters/SlidingWindow/RedisBase.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisBase.php rename to packages/abuse/src/Adapters/SlidingWindow/RedisBase.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisCluster.php b/packages/abuse/src/Adapters/SlidingWindow/RedisCluster.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisCluster.php rename to packages/abuse/src/Adapters/SlidingWindow/RedisCluster.php diff --git a/packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisPool.php b/packages/abuse/src/Adapters/SlidingWindow/RedisPool.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/SlidingWindow/RedisPool.php rename to packages/abuse/src/Adapters/SlidingWindow/RedisPool.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit.php b/packages/abuse/src/Adapters/TimeLimit.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit.php rename to packages/abuse/src/Adapters/TimeLimit.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php b/packages/abuse/src/Adapters/TimeLimit/Appwrite/TablesDB.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/Appwrite/TablesDB.php rename to packages/abuse/src/Adapters/TimeLimit/Appwrite/TablesDB.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/Database.php b/packages/abuse/src/Adapters/TimeLimit/Database.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/Database.php rename to packages/abuse/src/Adapters/TimeLimit/Database.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/None.php b/packages/abuse/src/Adapters/TimeLimit/None.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/None.php rename to packages/abuse/src/Adapters/TimeLimit/None.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/Redis.php b/packages/abuse/src/Adapters/TimeLimit/Redis.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/Redis.php rename to packages/abuse/src/Adapters/TimeLimit/Redis.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/RedisCluster.php b/packages/abuse/src/Adapters/TimeLimit/RedisCluster.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/RedisCluster.php rename to packages/abuse/src/Adapters/TimeLimit/RedisCluster.php diff --git a/packages/abuse/src/Abuse/Adapters/TimeLimit/RedisPool.php b/packages/abuse/src/Adapters/TimeLimit/RedisPool.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TimeLimit/RedisPool.php rename to packages/abuse/src/Adapters/TimeLimit/RedisPool.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket.php b/packages/abuse/src/Adapters/TokenBucket.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket.php rename to packages/abuse/src/Adapters/TokenBucket.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket/None.php b/packages/abuse/src/Adapters/TokenBucket/None.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket/None.php rename to packages/abuse/src/Adapters/TokenBucket/None.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket/Redis.php b/packages/abuse/src/Adapters/TokenBucket/Redis.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket/Redis.php rename to packages/abuse/src/Adapters/TokenBucket/Redis.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket/RedisBase.php b/packages/abuse/src/Adapters/TokenBucket/RedisBase.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket/RedisBase.php rename to packages/abuse/src/Adapters/TokenBucket/RedisBase.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket/RedisCluster.php b/packages/abuse/src/Adapters/TokenBucket/RedisCluster.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket/RedisCluster.php rename to packages/abuse/src/Adapters/TokenBucket/RedisCluster.php diff --git a/packages/abuse/src/Abuse/Adapters/TokenBucket/RedisPool.php b/packages/abuse/src/Adapters/TokenBucket/RedisPool.php similarity index 100% rename from packages/abuse/src/Abuse/Adapters/TokenBucket/RedisPool.php rename to packages/abuse/src/Adapters/TokenBucket/RedisPool.php diff --git a/packages/abuse/tests/Abuse/Appwrite/TablesDBTest.php b/packages/abuse/tests/E2E/Appwrite/TablesDBTest.php similarity index 94% rename from packages/abuse/tests/Abuse/Appwrite/TablesDBTest.php rename to packages/abuse/tests/E2E/Appwrite/TablesDBTest.php index 42fd0a450e4..1d9c023ba4e 100755 --- a/packages/abuse/tests/Abuse/Appwrite/TablesDBTest.php +++ b/packages/abuse/tests/E2E/Appwrite/TablesDBTest.php @@ -1,6 +1,6 @@ setEndpoint(\getenv('APPWRITE_ENDPOINT') ?: '') ->setProject(\getenv('APPWRITE_PROJECT_ID') ?: '') ->setKey(\getenv('APPWRITE_API_KEY') ?: ''); diff --git a/packages/abuse/tests/Abuse/Base.php b/packages/abuse/tests/E2E/Base.php similarity index 99% rename from packages/abuse/tests/Abuse/Base.php rename to packages/abuse/tests/E2E/Base.php index fcdb115c99a..4390245b304 100644 --- a/packages/abuse/tests/Abuse/Base.php +++ b/packages/abuse/tests/E2E/Base.php @@ -1,6 +1,6 @@ new \RedisCluster(null, Services::CLUSTER_SEEDS), timeout: 0.0); } public function getAdapter(string $key, int $limit, int $seconds): TimeLimit @@ -87,7 +80,6 @@ private function clearRedisClusterPoolLogs(): void do { /** @phpstan-ignore-next-line */ $keys = $redis->scan($cursor, $master, 'abuse__*', 100); - /** @phpstan-ignore-next-line */ if ($keys === false) { continue; } diff --git a/packages/abuse/tests/Abuse/RedisPoolTest.php b/packages/abuse/tests/E2E/RedisPoolTest.php similarity index 96% rename from packages/abuse/tests/Abuse/RedisPoolTest.php rename to packages/abuse/tests/E2E/RedisPoolTest.php index 9ac98aa083d..7fd4e57ea88 100644 --- a/packages/abuse/tests/Abuse/RedisPoolTest.php +++ b/packages/abuse/tests/E2E/RedisPoolTest.php @@ -1,6 +1,6 @@ connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; }, timeout: 0.0); diff --git a/packages/abuse/tests/Abuse/RedisTest.php b/packages/abuse/tests/E2E/RedisTest.php similarity index 90% rename from packages/abuse/tests/Abuse/RedisTest.php rename to packages/abuse/tests/E2E/RedisTest.php index 23517edf861..fe8d0a7cc3a 100644 --- a/packages/abuse/tests/Abuse/RedisTest.php +++ b/packages/abuse/tests/E2E/RedisTest.php @@ -1,6 +1,6 @@ connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; } diff --git a/packages/abuse/tests/E2E/Services.php b/packages/abuse/tests/E2E/Services.php new file mode 100644 index 00000000000..0b8375e8fa7 --- /dev/null +++ b/packages/abuse/tests/E2E/Services.php @@ -0,0 +1,18 @@ +connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; }, timeout: 0.0); diff --git a/packages/abuse/tests/Abuse/SlidingWindow/RedisTest.php b/packages/abuse/tests/E2E/SlidingWindow/RedisTest.php similarity index 85% rename from packages/abuse/tests/Abuse/SlidingWindow/RedisTest.php rename to packages/abuse/tests/E2E/SlidingWindow/RedisTest.php index 126cea22bc2..ba8008edb22 100644 --- a/packages/abuse/tests/Abuse/SlidingWindow/RedisTest.php +++ b/packages/abuse/tests/E2E/SlidingWindow/RedisTest.php @@ -1,9 +1,10 @@ connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; } diff --git a/packages/abuse/tests/Abuse/TokenBucket/Base.php b/packages/abuse/tests/E2E/TokenBucket/Base.php similarity index 99% rename from packages/abuse/tests/Abuse/TokenBucket/Base.php rename to packages/abuse/tests/E2E/TokenBucket/Base.php index e9ce08bd163..2f840349899 100644 --- a/packages/abuse/tests/Abuse/TokenBucket/Base.php +++ b/packages/abuse/tests/E2E/TokenBucket/Base.php @@ -1,6 +1,6 @@ connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; }, timeout: 0.0); diff --git a/packages/abuse/tests/Abuse/TokenBucket/RedisTest.php b/packages/abuse/tests/E2E/TokenBucket/RedisTest.php similarity index 85% rename from packages/abuse/tests/Abuse/TokenBucket/RedisTest.php rename to packages/abuse/tests/E2E/TokenBucket/RedisTest.php index bb1b94ade0e..51fc968b100 100644 --- a/packages/abuse/tests/Abuse/TokenBucket/RedisTest.php +++ b/packages/abuse/tests/E2E/TokenBucket/RedisTest.php @@ -1,9 +1,10 @@ connect('redis', 6379); + $redis->connect(Services::HOST, Services::REDIS_PORT); return $redis; } diff --git a/packages/abuse/tests/Abuse/NoneTest.php b/packages/abuse/tests/NoneTest.php similarity index 96% rename from packages/abuse/tests/Abuse/NoneTest.php rename to packages/abuse/tests/NoneTest.php index e68ea2cf343..fa833fa99dd 100644 --- a/packages/abuse/tests/Abuse/NoneTest.php +++ b/packages/abuse/tests/NoneTest.php @@ -1,6 +1,6 @@ redis = new Client(); - $this->redis->connect('redis', 6379); + $this->redis->connect(Services::HOST, Services::REDIS_PORT); $this->adapter = new TimeLimit('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); $this->abuse = new Abuse($this->adapter); } diff --git a/packages/abuse/tests/Abuse/Bench/RedisCluster.php b/packages/abuse/tests/bench/RedisCluster.php similarity index 72% rename from packages/abuse/tests/Abuse/Bench/RedisCluster.php rename to packages/abuse/tests/bench/RedisCluster.php index 0d17f7d175e..badb048ae4d 100644 --- a/packages/abuse/tests/Abuse/Bench/RedisCluster.php +++ b/packages/abuse/tests/bench/RedisCluster.php @@ -1,10 +1,11 @@ redis = new Client(null, ['redis-cluster-0:6379', 'redis-cluster-1:6379', 'redis-cluster-2:6379', 'redis-cluster-3:6379']); + $this->redis = new Client(null, Services::CLUSTER_SEEDS); $this->adapter = new RedisClusterAdapter('login-attempt-from-{{ip}}', 3, 60 * 5, $this->redis); $this->abuse = new Abuse($this->adapter); } diff --git a/rfc/monorepo.md b/rfc/monorepo.md index 7c9d1bcb6c5..d2cef8acf75 100644 --- a/rfc/monorepo.md +++ b/rfc/monorepo.md @@ -272,6 +272,7 @@ Exit: `composer.lock` contains no `utopia-php/*` package. - Collapse `||` compatibility constraints in package manifests to single ranges once every sibling is on the current major. - Delete duplicated test helpers (`tests/extensions/Queue/InMemoryConnection.php` versus the queue package's own fakes) and every Appwrite-side workaround that existed only because a library fix was waiting on a release. - Burn down every `packages/*/phpstan-baseline.neon` a package arrives with (abuse's Redis cluster log adapters need one under PHPStan 2). + - `abuse`: 44 findings (its standalone repository analysed it at level max under PHPStan 1): 35 in `src`, `array|true` `scan()` and `_masters()` replies merged, sorted and combined into log maps in the `RedisCluster` and `RedisPool` adapters of all three strategies, plus an integer passed to `curl_setopt()` in `ReCaptcha`; 9 in its e2e tests, always-true `instanceof` and `is_int()` checks, the cluster `scan()` reply iterated unnarrowed, and a column shape in `TablesDBTest`. - `auth`: 33 findings (it had no PHPStan config of its own): `mixed` out-parameters and results from `openssl_pkey_export()`, `openssl_pkey_get_details()` and `openssl_sign()` in the asymmetric issuer and verifier, integer arithmetic in the PHPass encoder, and array shapes in `AuthorizationDetails` and `ResourceIndicators`, plus decoded-claim arithmetic in its tests. - `cache`: 25 findings (level 5 in the monorepo): `mixed` from the Memcached and Hazelcast server stats and the `RedisCluster` node addresses, values passed to `Envelope::encode()` untyped, and casts of Redis replies in its multiplexing and leasable e2e tests. - `cdn`: 92 findings (its standalone repository analysed it at level 6): 50 in `src`, offset access and string concatenation on the `mixed` decoded API responses in the Fastly, Fastly TLS and Cloudflare providers and cache adapters, and `request()` results not narrowed to their declared shapes; 42 in its tests, assertions on decoded request bodies captured by `TestClient`. From e17934965df6c93a5d76c1b914892ea945f43e50 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:53:02 +0200 Subject: [PATCH 415/470] fix(queue): park on the cached spare when the re-read fails The consumer-info request before parking can fail. A throw there left the delivery in hand, possibly the server's last, and the rest of its batch unacknowledged. The failure is now reported and the cached threshold stands. Also types upgradeTls() in the test transport, so its baseline entry, which moved with every edit above it, is no longer needed. --- packages/queue/phpstan-baseline.neon | 6 --- packages/queue/src/Broker/Nats.php | 9 +++- packages/queue/tests/E2E/NatsBrokerTest.php | 57 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/queue/phpstan-baseline.neon b/packages/queue/phpstan-baseline.neon index f9b5e1de9a9..dfccceffbec 100644 --- a/packages/queue/phpstan-baseline.neon +++ b/packages/queue/phpstan-baseline.neon @@ -984,12 +984,6 @@ parameters: count: 2 path: tests/E2E/NatsBrokerTest.php - - - message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/E2E/NatsBrokerTest\.php\:893\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: tests/E2E/NatsBrokerTest.php - - message: '#^Method Utopia\\Queue\\Tests\\E2E\\NatsBrokerTest\:\:failedAcknowledgement\(\) return type has no value type specified in iterable type iterable\.$#' identifier: missingType.iterableValue diff --git a/packages/queue/src/Broker/Nats.php b/packages/queue/src/Broker/Nats.php index ffc72f3a1c1..eeb8c5058d7 100644 --- a/packages/queue/src/Broker/Nats.php +++ b/packages/queue/src/Broker/Nats.php @@ -746,7 +746,14 @@ private function pull(Queue $queue, int $timeout, int $max): array // The consumer is shared: a broker with a larger maxDeliver may have // reprovisioned it since this one cached its spare. Parking on a stale // threshold would take attempts the server still allows, so re-read it. - $spare = $this->spareDelivery[$key] = $this->spareOf($this->consumers[$key]->info(true)->config); + // If the read fails, the cached one stands: this may be the server's last + // delivery, and a throw here would leave it, and every message behind it + // in the batch, unacknowledged -- stranded, if no advisory is caught. + try { + $spare = $this->spareDelivery[$key] = $this->spareOf($this->consumers[$key]->info(true)->config); + } catch (\Throwable $error) { + $this->report($error); + } if ($spare !== null && $jsMessage->metadata()->numDelivered >= $spare) { $this->park($queue, $jsMessage, 'max deliveries exceeded'); diff --git a/packages/queue/tests/E2E/NatsBrokerTest.php b/packages/queue/tests/E2E/NatsBrokerTest.php index aa2b02996cb..aa692cd4ebb 100644 --- a/packages/queue/tests/E2E/NatsBrokerTest.php +++ b/packages/queue/tests/E2E/NatsBrokerTest.php @@ -9,6 +9,7 @@ use Utopia\NATS\Connection; use Utopia\NATS\ConnectionOptions; use Utopia\NATS\Exception\ConnectionException; +use Utopia\NATS\Exception\NatsException; use Utopia\NATS\JetStream\ConsumerConfig; use Utopia\NATS\JetStream\DiscardPolicy; use Utopia\NATS\JetStream\StorageType; @@ -938,6 +939,7 @@ public function readLine(?float $timeout = null): string return $this->inner->readLine($timeout); } + /** @param array $options */ public function upgradeTls(array $options): void { $this->inner->upgradeTls($options); @@ -1661,6 +1663,61 @@ public function testASpareDeliveryRaisedByAnotherBrokerIsNotParkedEarly(): void $large->close(); } + public function testASpareDeliveryIsParkedWhenTheThresholdCannotBeReRead(): void + { + // Re-reading the consumer before parking is a request, and it can fail. The + // delivery in hand may be the server's last, so it is parked on the cached + // threshold rather than dropped unacknowledged with the rest of its batch. + $url = getenv('NATS_URL') ?: 'nats://127.0.0.1:14225'; + $queue = new Queue('t_' . substr(md5(uniqid('', true)), 0, 8)); + + $dying = new Nats(Connection::connect($url), ackWait: 1.0, maxDeliver: 2); + $dying->publish($queue, ['poison' => true]); + $this->assertCount(1, $dying->receive($queue, 2)); + $this->assertCount(1, $dying->receive($queue, 2)); + $dying->close(); + + $transport = new TcpTransport(); + $fault = $this->createStub(Transport::class); + foreach (['connect', 'read', 'readLine', 'upgradeTls', 'isConnected', 'close'] as $method) { + $fault->method($method)->willReturnCallback($transport->$method(...)); + } + $state = new class () { + public bool $armed = false; + }; + $failure = new NatsException('consumer info unavailable'); + $fault->method('write')->willReturnCallback(static function (string $data) use ($transport, $state, $failure): int { + if ($state->armed && str_contains($data, 'CONSUMER.INFO')) { + $state->armed = false; + throw $failure; + } + return $transport->write($data); + }); + $reported = []; + $fresh = new Nats( + static fn (): Connection => Connection::connect(new ConnectionOptions( + servers: $url, + transportFactory: static fn (): Transport => $fault, + )), + ackWait: 1.0, + maxDeliver: 2, + onError: static function (\Throwable $error) use (&$reported): void { + $reported[] = $error; + }, + ); + $state->armed = true; // provisioning sends no CONSUMER.INFO, so the re-read is the first + + $this->assertSame([], $fresh->receive($queue, 3), 'an exhausted message must not reach a handler'); + $this->assertFalse($state->armed, 'the re-read failed'); + $this->assertSame([$failure], $reported, 'and was reported'); + + $js = Connection::connect($url)->jetStream(); + $this->assertSame(0, $js->getStreamInfo('Q_' . strtoupper($queue->name))->state->messages, 'work stream holds nothing'); + $this->assertSame(1, $js->getStreamInfo('Q_' . strtoupper($queue->name) . '_DEAD')->state->messages, 'the message is on the dead stream'); + + $fresh->close(); + } + // JetStream updates a consumer's num_pending asynchronously after the publish ack. private function pendingSettled(int $expected): int { From ec55235de8f3abf38070fd4a31bd025c945b4c67 Mon Sep 17 00:00:00 2001 From: AayushKrGupta Date: Tue, 29 Sep 2026 00:58:45 +0530 Subject: [PATCH 416/470] fix(functions): return 400 for malformed execution headers Fixes #13962 Reject non-string and non-null values in the Headers validator to prevent a TypeError when strlen() is called on non-string values. In Executions\Create, pass Exception::GENERAL_ARGUMENT_INVALID as the exception type rather than passing the description string and integer 400 as message. --- src/Appwrite/Functions/Validator/Headers.php | 7 ++++++- .../Functions/Http/Executions/Create.php | 2 +- .../Functions/FunctionsCustomServerTest.php | 21 +++++++++++++++++++ .../unit/Functions/Validator/HeadersTest.php | 20 ++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php index a18660fa2dc..caf3fa7f59e 100644 --- a/src/Appwrite/Functions/Validator/Headers.php +++ b/src/Appwrite/Functions/Validator/Headers.php @@ -24,7 +24,7 @@ public function __construct(protected bool $allowEmpty = true, protected int $ma */ public function getDescription(): string { - return 'Invalid headers: Alphanumeric characters or hyphens only, cannot start with "x-appwrite", maximum ' . $this->maxKeys . ' keys, and total size ' . $this->maxSize . '.'; + return 'Invalid headers: Keys must contain alphanumeric characters or hyphens only and cannot start with "x-appwrite", values must be strings, maximum ' . $this->maxKeys . ' keys, and total size ' . $this->maxSize . '.'; } /** @@ -55,6 +55,11 @@ public function isValid($value): bool return false; } + // Reject non-string values + if (!\is_string($val) && !\is_null($val)) { + return false; + } + $length = \strlen($key); if ($length === 0) { return false; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index e66054101c4..c3bca83671b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -166,7 +166,7 @@ public function action( // 'headers' validator $validator = new Headers(); if (!$validator->isValid($headers)) { - throw new Exception($validator->getDescription(), 400); + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $validator->getDescription()); } /* @var Document $function */ diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 95c36625151..24b2b765832 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2140,6 +2140,27 @@ public function testCreateExecution(): void ], $this->getHeaders()), []); $this->assertEquals(204, $execution['headers']['status-code']); }, 10000, 500); + + /** + * Test for FAILURE + */ + $execution = $this->createExecution($data['functionId'], [ + 'headers' => [ + 'X-Test' => ['bad'], + ], + ]); + + $this->assertEquals(400, $execution['headers']['status-code']); + $this->assertEquals('general_argument_invalid', $execution['body']['type']); + + $execution = $this->createExecution($data['functionId'], [ + 'headers' => [ + 'bad/name' => 'value', + ], + ]); + + $this->assertEquals(400, $execution['headers']['status-code']); + $this->assertEquals('general_argument_invalid', $execution['body']['type']); } finally { $this->cleanupFunction($functionId); } diff --git a/tests/unit/Functions/Validator/HeadersTest.php b/tests/unit/Functions/Validator/HeadersTest.php index 9cc48c9bb34..4f5831790ea 100644 --- a/tests/unit/Functions/Validator/HeadersTest.php +++ b/tests/unit/Functions/Validator/HeadersTest.php @@ -83,6 +83,26 @@ public function testValues(): void ]; $this->assertTrue($this->object->isValid($headers)); + $headers = [ + 'X-Header' => ['bad'], + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'X-Header' => 123, + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'X-Header' => true, + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'bad/name' => 'value', + ]; + $this->assertFalse($this->object->isValid($headers)); + $headers = [ true => 'value', ]; From 5fd5f5379de29554e499193d9a3949a87a795c91 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 29 Sep 2026 03:49:09 +0000 Subject: [PATCH 417/470] fix(detector): stop scoring bare package.json as a framework hit Every JS framework inherited package.json from JS::getFiles(), so any repo with only that file tied at score 1 and collapsed to Angular via fewest-parents. Clear the base file list so detection requires a framework-specific config file or dependency package. Fixes #13911 Co-authored-by: chiragaggarwal5k --- .../detector/src/Detection/Framework/JS.php | 7 ++- packages/detector/tests/DetectorTest.php | 43 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/detector/src/Detection/Framework/JS.php b/packages/detector/src/Detection/Framework/JS.php index c7452c21d5f..f07b21efe42 100644 --- a/packages/detector/src/Detection/Framework/JS.php +++ b/packages/detector/src/Detection/Framework/JS.php @@ -19,6 +19,11 @@ public function getPackages(): array */ public function getFiles(): array { - return ['package.json']; + // Do not score a bare package.json. Every JS framework inherits this + // base, so treating the lockfile manifest as a hit forces a multi-way + // tie (all score 1) that collapses to Angular via fewest-parents — + // even for lodash-only or tooling-only repos. Frameworks must match + // their own config files and/or dependency packages instead. + return []; } } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 37b5e898ea4..319047de232 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -320,6 +320,7 @@ public static function frameworkDataProvider(): array [['public', 'src', 'astro.config.mjs', 'package-lock.json', 'package.json', 'tsconfig.json'], 'astro', 'pnpm install', 'pnpm run build', './dist'], [['src', 'static', 'scripts', 'eslint.config.js', 'package.json', 'pnpm-lock.yaml', 'svelte.config.js', 'tsconfig.js', 'vite.config.js', 'vite.config.lib.js'], 'sveltekit', 'pnpm install', 'pnpm run build', './build'], [['index.html', 'style.css'], null, null, null, null], // Test for FAILURE + [['package.json'], null, null, null, null], // bare package.json must not pick Angular ]; } @@ -790,6 +791,48 @@ public function testFrameworkEdgeCases(string $assertion, array $files, string $ $this->assertSame($framework, $detection->getName(), $assertion); } + /** + * A package.json with no framework dependencies must not resolve to Angular + * (or any other framework) via a multi-way package.json path-match tie. + * + * @see https://github.com/appwrite/appwrite/issues/13911 + */ + public function testFrameworkDetectionReturnsNullWithoutFrameworkSignals(): void + { + $cases = [ + '{"dependencies":{"lodash":"^4"}}', + '{"devDependencies":{"turbo":"^2"}}', + '{}', + ]; + + foreach ($cases as $packageJson) { + $detector = new Framework('npm'); + $detector + ->addOption(new Analog()) + ->addOption(new Angular()) + ->addOption(new Astro()) + ->addOption(new Flutter()) + ->addOption(new Lynx()) + ->addOption(new NextJs()) + ->addOption(new Nuxt()) + ->addOption(new React()) + ->addOption(new ReactNative()) + ->addOption(new Remix()) + ->addOption(new Svelte()) + ->addOption(new SvelteKit()) + ->addOption(new TanStackStart()) + ->addOption(new Vue()); + + $detector->addInput('package.json', Framework::INPUT_FILE); + $detector->addInput($packageJson, Framework::INPUT_PACKAGES); + + $this->assertNull( + $detector->detect(), + "Expected null for package.json content: {$packageJson}" + ); + } + } + public function testTanStackStartAdapterDetection(): void { $fw = new TanStackStart(); From 93d9aae3406bef0a688d83700c91a6b376efcf1c Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 12:43:09 +0530 Subject: [PATCH 418/470] fix(functions): keep scalar execution header values valid Only arrays and objects break the header size check, so numeric and boolean values stay accepted as before. The description now follows the other validators and mentions the leading/trailing hyphen rule. --- src/Appwrite/Functions/Validator/Headers.php | 6 +++--- tests/unit/Functions/Validator/HeadersTest.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php index caf3fa7f59e..ba21089ec0f 100644 --- a/src/Appwrite/Functions/Validator/Headers.php +++ b/src/Appwrite/Functions/Validator/Headers.php @@ -24,7 +24,7 @@ public function __construct(protected bool $allowEmpty = true, protected int $ma */ public function getDescription(): string { - return 'Invalid headers: Keys must contain alphanumeric characters or hyphens only and cannot start with "x-appwrite", values must be strings, maximum ' . $this->maxKeys . ' keys, and total size ' . $this->maxSize . '.'; + return 'Headers must contain at most ' . $this->maxKeys . ' keys and ' . $this->maxSize . ' bytes in total. Valid key chars are a-z, A-Z, 0-9, and hyphen. Keys can\'t start or end with a hyphen, or start with "x-appwrite". Values can\'t be arrays or objects.'; } /** @@ -55,8 +55,8 @@ public function isValid($value): bool return false; } - // Reject non-string values - if (!\is_string($val) && !\is_null($val)) { + // Reject array and object values + if (!\is_scalar($val) && !\is_null($val)) { return false; } diff --git a/tests/unit/Functions/Validator/HeadersTest.php b/tests/unit/Functions/Validator/HeadersTest.php index 4f5831790ea..22bf601791d 100644 --- a/tests/unit/Functions/Validator/HeadersTest.php +++ b/tests/unit/Functions/Validator/HeadersTest.php @@ -91,12 +91,12 @@ public function testValues(): void $headers = [ 'X-Header' => 123, ]; - $this->assertFalse($this->object->isValid($headers)); + $this->assertTrue($this->object->isValid($headers)); $headers = [ 'X-Header' => true, ]; - $this->assertFalse($this->object->isValid($headers)); + $this->assertTrue($this->object->isValid($headers)); $headers = [ 'bad/name' => 'value', From 00c64b43064daeb686d7eb4bf9e71d4e822f5f4e Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 13:00:46 +0530 Subject: [PATCH 419/470] chore: bump self-hosted console to 1.2.5 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e551fef2634..b98bcf341d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -270,7 +270,7 @@ services: max-file: "5" max-size: 10m container_name: appwrite-console - image: appwrite/new:1.1.180-self-hosted + image: appwrite/new:1.2.5-self-hosted restart: unless-stopped networks: - appwrite From 3b6eb03257b54548e5580d305c21a85d38513a2a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 13:18:30 +0530 Subject: [PATCH 420/470] refactor(teams): name the role length limit once Team create and both membership actions each spelled the role limit as a bare 81, repeated it in their parameter descriptions, and carried the same comment explaining where it comes from. Define it once as APP_LIMIT_ROLE_LENGTH and use it in all three, so the validators and the documented limit cannot drift apart. The rendered descriptions are unchanged. The create-team test now sends a project-scoped role longer than a bare ID, rather than a string built to the limit, and reads the creator's membership back to confirm the role was saved. --- app/init/constants.php | 1 + .../Modules/Teams/Http/Memberships/Create.php | 2 +- .../Modules/Teams/Http/Memberships/Update.php | 2 +- .../Platform/Modules/Teams/Http/Teams/Create.php | 2 +- tests/e2e/Services/Teams/TeamsBase.php | 15 +++++++++++++-- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index f51b57ffc92..67b1efeb3ae 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -41,6 +41,7 @@ const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value const APP_LIMIT_ARRAY_SCOPES_SIZE = 200; // Default maximum of how many scope elements can there be in API parameter that expects array value const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length. +const APP_LIMIT_ROLE_LENGTH = 81; // Maximum length of a team role: `project--` is 9 template characters around two 36-character IDs const APP_LIMIT_SUBQUERY = 1000; const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 25; diff --git a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php index d4aee458e76..824880c0d45 100644 --- a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php +++ b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Create.php @@ -80,7 +80,7 @@ public function __construct() ->param('email', '', new EmailValidator(), 'Email of the new team member.', true) ->param('userId', '', new UID(), 'ID of the user to be added to a team.', true) ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) - ->param('roles', [], new ArrayList(new Key(maxLength: 81), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 81 characters long.', false, ['project'], example: '["editor"]') // For project-specific permissions, roles will be in the format `project--`. Template takes 9 characters, `projectId` and `role` can be upto 36 characters. In total, 81 characters. + ->param('roles', [], new ArrayList(new Key(maxLength: APP_LIMIT_ROLE_LENGTH), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each ' . APP_LIMIT_ROLE_LENGTH . ' characters long.', false, ['project'], example: '["editor"]') ->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page ->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true) ->inject('response') diff --git a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php index 6bb0a720696..4e72cea4fbd 100644 --- a/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php +++ b/src/Appwrite/Platform/Modules/Teams/Http/Memberships/Update.php @@ -55,7 +55,7 @@ public function __construct() )) ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') - ->param('roles', [], new ArrayList(new Key(maxLength: 81), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 81 characters long.', false, ['project'], example: '["editor"]') // For project-specific permissions, roles will be in the format `project--`. Template takes 9 characters, `projectId` and `role` can be upto 36 characters. In total, 81 characters. + ->param('roles', [], new ArrayList(new Key(maxLength: APP_LIMIT_ROLE_LENGTH), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each ' . APP_LIMIT_ROLE_LENGTH . ' characters long.', false, ['project'], example: '["editor"]') ->inject('request') ->inject('response') ->inject('user') diff --git a/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php b/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php index 911fdf403af..df998f66449 100644 --- a/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php +++ b/src/Appwrite/Platform/Modules/Teams/Http/Teams/Create.php @@ -62,7 +62,7 @@ public function __construct() )) ->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', null, new Text(128), 'Team name. Max length: 128 chars.') - ->param('roles', ['owner'], new ArrayList(new Key(maxLength: 81), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 81 characters long.', true) // For project-specific permissions, roles will be in the format `project--`. Template takes 9 characters, `projectId` and `role` can be upto 36 characters. In total, 81 characters. + ->param('roles', ['owner'], new ArrayList(new Key(maxLength: APP_LIMIT_ROLE_LENGTH), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each ' . APP_LIMIT_ROLE_LENGTH . ' characters long.', true) ->inject('response') ->inject('user') ->inject('dbForProject') diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 7c219abf52a..d0e0548ab87 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -166,18 +166,29 @@ public function testCreateTeam(): void $this->assertIsInt($response3['body']['total']); $this->assertEquals(true, $dateValidator->isValid($response3['body']['$createdAt'])); - // A project-scoped role fills the full `project--` budget of 81 chars + // A project-scoped role is longer than a bare ID + $role = 'project-' . ID::unique() . '-developer'; $response4 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'teamId' => ID::unique(), 'name' => 'Aston Villa', - 'roles' => [\str_repeat('a', 81)], + 'roles' => [$role], ]); $this->assertEquals(201, $response4['headers']['status-code']); + // An API key creates the team without a creator membership to read the role from + if ($this->getSide() !== 'server') { + $memberships = $this->client->call(Client::METHOD_GET, '/teams/' . $response4['body']['$id'] . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertContains($role, $memberships['body']['memberships'][0]['roles']); + } + /** * Test for FAILURE */ From f91416ee2c274adf48e756e97617723e1b089313 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:11:40 +0100 Subject: [PATCH 421/470] chore(nats): mirror plumbing --- packages/nats/.github/workflows/mirror.yml | 2 +- packages/nats/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nats/.github/workflows/mirror.yml b/packages/nats/.github/workflows/mirror.yml index 469a093888b..167c0f5f1bb 100644 --- a/packages/nats/.github/workflows/mirror.yml +++ b/packages/nats/.github/workflows/mirror.yml @@ -12,6 +12,6 @@ permissions: jobs: redirect: - uses: utopia-php/monorepo/.github/workflows/mirror-redirect.yml@main + uses: appwrite/appwrite/.github/workflows/mirror-redirect.yml@main with: package: nats diff --git a/packages/nats/README.md b/packages/nats/README.md index 23015f49d91..2b18eefb375 100644 --- a/packages/nats/README.md +++ b/packages/nats/README.md @@ -1,7 +1,7 @@ # nats.php > [!IMPORTANT] -> This repository is a read-only mirror of the [utopia-php monorepo](https://github.com/utopia-php/monorepo). Development happens in [`packages/nats`](https://github.com/utopia-php/monorepo/tree/main/packages/nats) — please open issues and pull requests there. +> This repository is a read-only mirror of [`packages/nats`](https://github.com/appwrite/appwrite/tree/main/packages/nats) in [appwrite/appwrite](https://github.com/appwrite/appwrite). Development happens there — please open issues and pull requests against appwrite/appwrite. A modern PHP client for [NATS](https://nats.io) messaging system with JetStream and Key-Value store support. From 8d5066a15a673bd7208773f1cd7e5fb8e3a24415 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:14:59 +0100 Subject: [PATCH 422/470] refactor: load nats from packages/ --- composer.json | 3 + composer.lock | 2 +- packages/nats/README.md | 10 +- packages/nats/docker-compose.yml | 2 + packages/nats/phpstan-baseline.neon | 2047 +++++++++++++++++ packages/nats/phpstan.neon | 6 +- packages/nats/phpunit.xml | 5 +- packages/nats/rector.php | 23 + packages/nats/src/Auth/TokenAuth.php | 3 +- packages/nats/src/Auth/UserPassAuth.php | 3 +- packages/nats/src/Connection.php | 6 +- .../src/Exception/AuthenticationException.php | 4 +- .../src/Exception/ConnectionException.php | 4 +- .../nats/src/Exception/KeyValueException.php | 4 +- .../src/Exception/MaxPayloadException.php | 4 +- packages/nats/src/Exception/NatsException.php | 4 +- .../src/Exception/ObjectStoreException.php | 4 +- .../src/Exception/PermissionException.php | 4 +- .../nats/src/Exception/ProtocolException.php | 4 +- .../nats/src/Exception/TimeoutException.php | 4 +- packages/nats/src/JetStream/AccountInfo.php | 3 +- packages/nats/src/JetStream/ApiError.php | 3 +- packages/nats/src/JetStream/Consumer.php | 3 +- .../nats/src/JetStream/ConsumerConfig.php | 3 +- packages/nats/src/JetStream/ConsumerInfo.php | 3 +- .../nats/src/JetStream/ConsumerLimits.php | 3 +- .../nats/src/JetStream/ExternalStream.php | 3 +- packages/nats/src/JetStream/JetStream.php | 2 +- .../nats/src/JetStream/JetStreamMessage.php | 3 +- packages/nats/src/JetStream/MessageBatch.php | 3 +- packages/nats/src/JetStream/MsgMetadata.php | 3 +- packages/nats/src/JetStream/Placement.php | 3 +- packages/nats/src/JetStream/PubAck.php | 3 +- packages/nats/src/JetStream/Republish.php | 3 +- packages/nats/src/JetStream/SequenceInfo.php | 3 +- packages/nats/src/JetStream/Stream.php | 3 +- packages/nats/src/JetStream/StreamConfig.php | 5 +- packages/nats/src/JetStream/StreamInfo.php | 3 +- packages/nats/src/JetStream/StreamMessage.php | 3 +- packages/nats/src/JetStream/StreamSource.php | 5 +- packages/nats/src/JetStream/StreamState.php | 3 +- .../nats/src/JetStream/SubjectTransform.php | 3 +- packages/nats/src/KeyValue/KeyValue.php | 3 +- packages/nats/src/KeyValue/KeyValueConfig.php | 3 +- packages/nats/src/KeyValue/KeyValueEntry.php | 3 +- packages/nats/src/KeyValue/KeyValueStatus.php | 3 +- .../src/KeyValue/KeyValueWatchOptions.php | 3 +- packages/nats/src/Message.php | 3 +- packages/nats/src/ObjectStore/ObjectLink.php | 3 +- packages/nats/src/ObjectStore/ObjectMeta.php | 5 +- packages/nats/src/ObjectStore/ObjectStore.php | 5 +- .../src/ObjectStore/ObjectStoreConfig.php | 3 +- packages/nats/src/Protocol/Parser.php | 7 +- packages/nats/src/ServerInfo.php | 3 +- packages/nats/src/Services/Group.php | 3 +- packages/nats/src/Services/Service.php | 10 +- .../nats/src/Transport/SwooleTransport.php | 3 +- packages/nats/src/Transport/TlsTransport.php | 3 +- .../nats/src/Transport/WebSocketTransport.php | 3 +- .../tests/{Unit => }/Auth/NKeyAuthTest.php | 2 +- .../ConnectionDeathDetectionTest.php | 14 +- .../tests/{Unit => }/ConnectionExtrasTest.php | 8 +- .../{Unit => }/ConnectionProtocolTest.php | 6 +- .../{Unit => }/ConnectionTlsAndAuthTest.php | 8 +- .../{Unit => }/ConsumerFetchLifecycleTest.php | 6 +- .../{Unit => }/ConsumerPullRequestTest.php | 6 +- .../nats/tests/E2E/JetStreamParityTest.php | 2 +- .../tests/E2E/KeyValueWatchOptionsTest.php | 2 +- .../nats/tests/E2E/ObjectStoreExtraTest.php | 6 +- packages/nats/tests/E2E/ObjectStoreTest.php | 2 +- packages/nats/tests/E2E/RequestManyTest.php | 2 +- packages/nats/tests/E2E/RequestReplyTest.php | 2 +- packages/nats/tests/E2E/ServiceExtrasTest.php | 14 +- packages/nats/tests/E2E/ServiceTest.php | 4 +- packages/nats/tests/E2E/WebSocketTest.php | 4 +- .../nats/tests/{Unit => }/HeadersTest.php | 2 +- packages/nats/tests/{Unit => }/InboxTest.php | 2 +- .../tests/{Unit => }/JetStream/ConfigTest.php | 2 +- .../{Unit => }/ParserFrameIntegrityTest.php | 4 +- .../tests/{Unit => }/Protocol/ParserTest.php | 18 +- .../tests/{Unit => }/Protocol/WriterTest.php | 2 +- .../tests/{Unit => }/ReconnectBackoffTest.php | 2 +- .../nats/tests/{Unit => }/RequestsTest.php | 44 +- .../SubscriptionSlowConsumerTest.php | 2 +- .../{Unit => }/Support/FakeTransport.php | 6 +- .../{Unit => }/Support/PartialWriteStream.php | 2 +- .../Transport/TcpTransportNoDelayTest.php | 2 +- .../Transport/TcpTransportWriteTest.php | 4 +- .../{Unit => }/WriteFailureSemanticsTest.php | 6 +- 89 files changed, 2308 insertions(+), 159 deletions(-) create mode 100644 packages/nats/phpstan-baseline.neon create mode 100644 packages/nats/rector.php rename packages/nats/tests/{Unit => }/Auth/NKeyAuthTest.php (99%) rename packages/nats/tests/{Unit => }/ConnectionDeathDetectionTest.php (97%) rename packages/nats/tests/{Unit => }/ConnectionExtrasTest.php (93%) rename packages/nats/tests/{Unit => }/ConnectionProtocolTest.php (95%) rename packages/nats/tests/{Unit => }/ConnectionTlsAndAuthTest.php (92%) rename packages/nats/tests/{Unit => }/ConsumerFetchLifecycleTest.php (96%) rename packages/nats/tests/{Unit => }/ConsumerPullRequestTest.php (97%) rename packages/nats/tests/{Unit => }/HeadersTest.php (99%) rename packages/nats/tests/{Unit => }/InboxTest.php (96%) rename packages/nats/tests/{Unit => }/JetStream/ConfigTest.php (98%) rename packages/nats/tests/{Unit => }/ParserFrameIntegrityTest.php (97%) rename packages/nats/tests/{Unit => }/Protocol/ParserTest.php (95%) rename packages/nats/tests/{Unit => }/Protocol/WriterTest.php (98%) rename packages/nats/tests/{Unit => }/ReconnectBackoffTest.php (98%) rename packages/nats/tests/{Unit => }/RequestsTest.php (91%) rename packages/nats/tests/{Unit => }/SubscriptionSlowConsumerTest.php (99%) rename packages/nats/tests/{Unit => }/Support/FakeTransport.php (98%) rename packages/nats/tests/{Unit => }/Support/PartialWriteStream.php (95%) rename packages/nats/tests/{Unit => }/Transport/TcpTransportNoDelayTest.php (98%) rename packages/nats/tests/{Unit => }/Transport/TcpTransportWriteTest.php (94%) rename packages/nats/tests/{Unit => }/WriteFailureSemanticsTest.php (95%) diff --git a/composer.json b/composer.json index f16a23e3df4..53cf04a5984 100644 --- a/composer.json +++ b/composer.json @@ -50,6 +50,7 @@ "Utopia\\Lock\\": "packages/lock/src", "Utopia\\Messaging\\": "packages/messaging/src", "Utopia\\Mqtt\\": "packages/mqtt/src", + "Utopia\\NATS\\": "packages/nats/src", "Utopia\\OpenAPI\\": "packages/openapi/src", "Utopia\\Platform\\": "packages/platform/src", "Utopia\\Pools\\": "packages/pools/src", @@ -104,6 +105,7 @@ "Utopia\\Lock\\Tests\\": "packages/lock/tests", "Utopia\\Messaging\\Tests\\": "packages/messaging/tests", "Utopia\\Mqtt\\Tests\\": "packages/mqtt/tests", + "Utopia\\NATS\\Tests\\": "packages/nats/tests", "Utopia\\OpenAPI\\Tests\\": "packages/openapi/tests", "Utopia\\Platform\\Tests\\": "packages/platform/tests", "Utopia\\Pools\\Tests\\": "packages/pools/tests", @@ -220,6 +222,7 @@ "utopia-php/lock": "*", "utopia-php/messaging": "*", "utopia-php/mqtt": "*", + "utopia-php/nats": "*", "utopia-php/openapi": "*", "utopia-php/platform": "*", "utopia-php/pools": "*", diff --git a/composer.lock b/composer.lock index 221c4d1f144..c74fd83f85d 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": "ca7dc18250a147b8f99c6644007025a9", + "content-hash": "386999fdf620ccb4f4663f9dd69a7c5e", "packages": [ { "name": "adhocore/jwt", diff --git a/packages/nats/README.md b/packages/nats/README.md index 2b18eefb375..b934ecff978 100644 --- a/packages/nats/README.md +++ b/packages/nats/README.md @@ -350,13 +350,15 @@ $conn = Connection::connect(new ConnectionOptions( ```bash # Unit tests -./vendor/bin/phpunit --testsuite unit +composer test -# Integration tests (requires a running nats-server) -./vendor/bin/phpunit --testsuite integration +# E2E tests (against the NATS servers in docker-compose.yml) +docker compose up -d --wait +composer test:e2e +docker compose down -v # With custom NATS URL -NATS_URL=nats://host:4222 ./vendor/bin/phpunit --testsuite integration +NATS_URL=nats://host:4222 composer test:e2e ``` ## Batched requests diff --git a/packages/nats/docker-compose.yml b/packages/nats/docker-compose.yml index 5431b73a16f..8159350dbc6 100644 --- a/packages/nats/docker-compose.yml +++ b/packages/nats/docker-compose.yml @@ -1,3 +1,5 @@ +name: utopia-nats + services: nats: image: nats:2.11-alpine diff --git a/packages/nats/phpstan-baseline.neon b/packages/nats/phpstan-baseline.neon new file mode 100644 index 00000000000..7f57a8f903d --- /dev/null +++ b/packages/nats/phpstan-baseline.neon @@ -0,0 +1,2047 @@ +parameters: + ignoreErrors: + - + message: '#^Parameter \#1 \$codepoint of function chr expects int\<0, 255\>, int given\.$#' + identifier: argument.type + count: 1 + path: src/Auth/NKeyAuth.php + + - + message: '#^Parameter \#1 \$seed of function sodium_crypto_sign_seed_keypair expects non\-empty\-string, string given\.$#' + identifier: argument.type + count: 2 + path: src/Auth/NKeyAuth.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 2 + path: src/Connection.php + + - + message: '#^Method Utopia\\NATS\\Connection\:\:buildConnectPayload\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Connection.php + + - + message: '#^Method Utopia\\NATS\\Connection\:\:handleMessage\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \#1 \$data of method Utopia\\NATS\\Connection\:\:handleMessage\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\ServerInfo\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \#1 \$raw of static method Utopia\\NATS\\Headers\:\:fromWire\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \#1 \$url of method Utopia\\NATS\\Connection\:\:normalizeUrl\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/Connection.php + + - + message: '#^Parameter \$data of class Utopia\\NATS\\Message constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \$replyTo of class Utopia\\NATS\\Message constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \$sid of class Utopia\\NATS\\Message constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Parameter \$subject of class Utopia\\NATS\\Message constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Connection.php + + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 2 + path: src/Connection.php + + - + message: '#^Property Utopia\\NATS\\Connection\:\:\$subscriptions \(array\\) does not accept array\\.$#' + identifier: assign.propertyType + count: 1 + path: src/Connection.php + + - + message: '#^Property Utopia\\NATS\\Connection\:\:\$transport \(Utopia\\NATS\\Transport\\Transport\) does not accept mixed\.$#' + identifier: assign.propertyType + count: 1 + path: src/Connection.php + + - + message: '#^Class Utopia\\NATS\\Headers implements generic interface IteratorAggregate but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Headers.php + + - + message: '#^Method Utopia\\NATS\\Headers\:\:getIterator\(\) return type with generic class ArrayIterator does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Headers.php + + - + message: '#^Cannot access offset ''errors'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Cannot access offset ''total'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\AccountInfo\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$apiErrors of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$apiTotal of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$consumers of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$domain of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$limits of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects array\, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$memory of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$raw of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$storage of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Parameter \$streams of class Utopia\\NATS\\JetStream\\AccountInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/AccountInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ApiError\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ApiError.php + + - + message: '#^Parameter \$code of class Utopia\\NATS\\JetStream\\ApiError constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ApiError.php + + - + message: '#^Parameter \$description of class Utopia\\NATS\\JetStream\\ApiError constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ApiError.php + + - + message: '#^Parameter \$errCode of class Utopia\\NATS\\JetStream\\ApiError constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ApiError.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ConsumerInfo\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Consumer.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\JetStream\:\:checkError\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Consumer.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ConsumerConfig\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ConsumerConfig\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(int\)\: float given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#1 \$nanos of static method Utopia\\NATS\\JetStream\\StreamConfig\:\:nanosToSeconds\(\) expects int, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\AckPolicy\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\DeliverPolicy\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\ReplayPolicy\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \#2 \$array of function array_map expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$backoff of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects list\\|null, array\\|null given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$deliverGroup of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$deliverSubject of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$description of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$durableName of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$filterSubject of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$filterSubjects of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects list\\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$flowControl of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$maxAckPending of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$maxBatch of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$maxBytes of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$maxDeliver of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$maxWaiting of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$memStorage of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$metadata of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects array\\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$name of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$numReplicas of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$optStartSeq of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Parameter \$optStartTime of class Utopia\\NATS\\JetStream\\ConsumerConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerConfig.php + + - + message: '#^Cannot access offset ''name'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ConsumerInfo\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ConsumerConfig\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\SequenceInfo\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$created of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$name of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$numAckPending of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$numPending of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$numRedelivered of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$numWaiting of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$pushBound of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Parameter \$streamName of class Utopia\\NATS\\JetStream\\ConsumerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ConsumerLimits\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ConsumerLimits.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ConsumerLimits\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ConsumerLimits.php + + - + message: '#^Parameter \#1 \$nanos of static method Utopia\\NATS\\JetStream\\StreamConfig\:\:nanosToSeconds\(\) expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerLimits.php + + - + message: '#^Parameter \$maxAckPending of class Utopia\\NATS\\JetStream\\ConsumerLimits constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ConsumerLimits.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\ExternalStream\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/ExternalStream.php + + - + message: '#^Parameter \$api of class Utopia\\NATS\\JetStream\\ExternalStream constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ExternalStream.php + + - + message: '#^Parameter \$deliver of class Utopia\\NATS\\JetStream\\ExternalStream constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/ExternalStream.php + + - + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:apiRequest\(\) has parameter \$payload with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:apiRequest\(\) should return array\ but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:checkError\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:getConsumerNames\(\) should return list\ but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:getStreamNames\(\) should return list\ but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\JetStream\:\:listStreams\(\) should return list\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(array\)\: Utopia\\NATS\\JetStream\\StreamInfo given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ApiError\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ConsumerInfo\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\JetStream\:\:checkError\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\PubAck\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\StreamMessage\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/JetStream/JetStream.php + + - + message: '#^Parameter \#2 \$array of function array_map expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/JetStream.php + + - + message: '#^Class Utopia\\NATS\\JetStream\\MessageBatch implements generic interface IteratorAggregate but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/JetStream/MessageBatch.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\MessageBatch\:\:getIterator\(\) return type with generic class ArrayIterator does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/JetStream/MessageBatch.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\Placement\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/Placement.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\Placement\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/Placement.php + + - + message: '#^Parameter \$cluster of class Utopia\\NATS\\JetStream\\Placement constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Placement.php + + - + message: '#^Parameter \$tags of class Utopia\\NATS\\JetStream\\Placement constructor expects list\\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Placement.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\PubAck\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/PubAck.php + + - + message: '#^Parameter \$domain of class Utopia\\NATS\\JetStream\\PubAck constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/PubAck.php + + - + message: '#^Parameter \$duplicate of class Utopia\\NATS\\JetStream\\PubAck constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/PubAck.php + + - + message: '#^Parameter \$sequence of class Utopia\\NATS\\JetStream\\PubAck constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/PubAck.php + + - + message: '#^Parameter \$stream of class Utopia\\NATS\\JetStream\\PubAck constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/PubAck.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\Republish\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/Republish.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\Republish\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/Republish.php + + - + message: '#^Parameter \$destination of class Utopia\\NATS\\JetStream\\Republish constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Republish.php + + - + message: '#^Parameter \$headersOnly of class Utopia\\NATS\\JetStream\\Republish constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Republish.php + + - + message: '#^Parameter \$source of class Utopia\\NATS\\JetStream\\Republish constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/Republish.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\SequenceInfo\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/SequenceInfo.php + + - + message: '#^Parameter \$consumerSeq of class Utopia\\NATS\\JetStream\\SequenceInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/SequenceInfo.php + + - + message: '#^Parameter \$lastActive of class Utopia\\NATS\\JetStream\\SequenceInfo constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/SequenceInfo.php + + - + message: '#^Parameter \$streamSeq of class Utopia\\NATS\\JetStream\\SequenceInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/SequenceInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamConfig\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamConfig\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(array\)\: Utopia\\NATS\\JetStream\\StreamSource given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ConsumerLimits\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\Placement\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\Republish\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\StreamSource\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\SubjectTransform\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$nanos of static method Utopia\\NATS\\JetStream\\StreamConfig\:\:nanosToSeconds\(\) expects int, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\DiscardPolicy\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\RetentionPolicy\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#1 \$value of static method Utopia\\NATS\\JetStream\\StorageType\:\:tryFrom\(\) expects int\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \#2 \$array of function array_map expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$allowDirect of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$allowMsgTtl of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$allowRollup of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$compression of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$denyDelete of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$denyPurge of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$description of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$firstSeq of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$maxBytes of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$maxConsumers of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$maxMsgSize of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$maxMsgs of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$maxMsgsPerSubject of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$metadata of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects array\\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$mirrorDirect of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$name of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$noAck of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$replicas of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$sealed of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$sources of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects list\\|null, array\\|null given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Parameter \$subjects of class Utopia\\NATS\\JetStream\\StreamConfig constructor expects list\, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamConfig.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamInfo\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamInfo.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\StreamConfig\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamInfo.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\StreamState\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamInfo.php + + - + message: '#^Parameter \$created of class Utopia\\NATS\\JetStream\\StreamInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamInfo.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamMessage\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamMessage.php + + - + message: '#^Parameter \#1 \$string of function base64_decode expects string, mixed given\.$#' + identifier: argument.type + count: 2 + path: src/JetStream/StreamMessage.php + + - + message: '#^Parameter \$sequence of class Utopia\\NATS\\JetStream\\StreamMessage constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamMessage.php + + - + message: '#^Parameter \$subject of class Utopia\\NATS\\JetStream\\StreamMessage constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamMessage.php + + - + message: '#^Parameter \$time of class Utopia\\NATS\\JetStream\\StreamMessage constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamMessage.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamSource\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamSource\:\:toArray\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(array\)\: Utopia\\NATS\\JetStream\\SubjectTransform given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\ExternalStream\:\:fromArray\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \#2 \$array of function array_map expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \$filterSubject of class Utopia\\NATS\\JetStream\\StreamSource constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \$name of class Utopia\\NATS\\JetStream\\StreamSource constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \$optStartSeq of class Utopia\\NATS\\JetStream\\StreamSource constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \$optStartTime of class Utopia\\NATS\\JetStream\\StreamSource constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Parameter \$subjectTransforms of class Utopia\\NATS\\JetStream\\StreamSource constructor expects list\\|null, array\\|null given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamSource.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\StreamState\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$bytes of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$consumerCount of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$firstSeq of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$firstTs of class Utopia\\NATS\\JetStream\\StreamState constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$lastSeq of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$lastTs of class Utopia\\NATS\\JetStream\\StreamState constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$messages of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$numDeleted of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Parameter \$numSubjects of class Utopia\\NATS\\JetStream\\StreamState constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/StreamState.php + + - + message: '#^Method Utopia\\NATS\\JetStream\\SubjectTransform\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/JetStream/SubjectTransform.php + + - + message: '#^Parameter \$destination of class Utopia\\NATS\\JetStream\\SubjectTransform constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/SubjectTransform.php + + - + message: '#^Parameter \$source of class Utopia\\NATS\\JetStream\\SubjectTransform constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/JetStream/SubjectTransform.php + + - + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot access offset ''message'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot access offset ''num_pending'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot access offset ''state'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot access offset ''subjects'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot call method ack\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int + count: 2 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 3 + path: src/KeyValue/KeyValue.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\JetStream\:\:checkError\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 4 + path: src/KeyValue/KeyValue.php + + - + message: '#^Parameter \#2 \$msg of method Utopia\\NATS\\KeyValue\\KeyValue\:\:entryFromDelivered\(\) expects Utopia\\NATS\\JetStream\\JetStreamMessage, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Parameter \$created of class Utopia\\NATS\\KeyValue\\KeyValueEntry constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/KeyValue/KeyValue.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 2 + path: src/ObjectStore/ObjectLink.php + + - + message: '#^Cannot access offset ''link'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectMeta.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int + count: 2 + path: src/ObjectStore/ObjectMeta.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 7 + path: src/ObjectStore/ObjectMeta.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\ObjectStore\\ObjectLink\:\:fromArray\(\) expects array\, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/ObjectStore/ObjectMeta.php + + - + message: '#^Parameter \$metadata of class Utopia\\NATS\\ObjectStore\\ObjectMeta constructor expects array\\|null, array\\|null given\.$#' + identifier: argument.type + count: 1 + path: src/ObjectStore/ObjectMeta.php + + - + message: '#^Binary operation "\.\=" between string and mixed results in an error\.$#' + identifier: assignOp.invalid + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot access offset ''config'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot access offset ''data'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot access offset ''error'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot access offset ''message'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot access offset ''seq'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot call method ack\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot call method getData\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int + count: 1 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 2 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\JetStream\\JetStream\:\:checkError\(\) expects array, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Parameter \#1 \$data of static method Utopia\\NATS\\ObjectStore\\ObjectMeta\:\:fromArray\(\) expects array\, array\ given\.$#' + identifier: argument.type + count: 3 + path: src/ObjectStore/ObjectStore.php + + - + message: '#^Method Utopia\\NATS\\Protocol\\Writer\:\:connect\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Protocol/Writer.php + + - + message: '#^Method Utopia\\NATS\\ServerInfo\:\:__construct\(\) has parameter \$connectUrls with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/ServerInfo.php + + - + message: '#^Method Utopia\\NATS\\ServerInfo\:\:fromArray\(\) has parameter \$data with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$authRequired of class Utopia\\NATS\\ServerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$clientId of class Utopia\\NATS\\ServerInfo constructor expects int\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$clientIp of class Utopia\\NATS\\ServerInfo constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$connectUrls of class Utopia\\NATS\\ServerInfo constructor expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$headersSupported of class Utopia\\NATS\\ServerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$host of class Utopia\\NATS\\ServerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$jetstream of class Utopia\\NATS\\ServerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$maxPayload of class Utopia\\NATS\\ServerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$nonce of class Utopia\\NATS\\ServerInfo constructor expects string\|null, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$port of class Utopia\\NATS\\ServerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$proto of class Utopia\\NATS\\ServerInfo constructor expects int, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$serverId of class Utopia\\NATS\\ServerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$serverName of class Utopia\\NATS\\ServerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$tlsAvailable of class Utopia\\NATS\\ServerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$tlsRequired of class Utopia\\NATS\\ServerInfo constructor expects bool, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Parameter \$version of class Utopia\\NATS\\ServerInfo constructor expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServerInfo.php + + - + message: '#^Cannot access property \$data on mixed\.$#' + identifier: property.nonObject + count: 1 + path: src/Subscription.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 1 + path: src/Subscription.php + + - + message: '#^Method Utopia\\NATS\\Subscription\:\:nextMessage\(\) should return Utopia\\NATS\\Message\|null but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/Subscription.php + + - + message: '#^Property Utopia\\NATS\\Subscription\:\:\$pendingMessages with generic class SplQueue does not specify its types\: TValue$#' + identifier: missingType.generics + count: 1 + path: src/Subscription.php + + - + message: '#^Binary operation "\+\=" between float\|int and mixed results in an error\.$#' + identifier: assignOp.invalid + count: 1 + path: src/Transport/SwooleTransport.php + + - + message: '#^Binary operation "\.\=" between string and mixed results in an error\.$#' + identifier: assignOp.invalid + count: 1 + path: src/Transport/SwooleTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\SwooleTransport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/SwooleTransport.php + + - + message: '#^Parameter \#1 \$options of method Utopia\\NATS\\Transport\\SwooleTransport\:\:buildTlsSettings\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/SwooleTransport.php + + - + message: '#^Part \$client\-\>errCode \(mixed\) of encapsed string cannot be cast to string\.$#' + identifier: encapsedStringPart.nonString + count: 3 + path: src/Transport/SwooleTransport.php + + - + message: '#^Part \$client\-\>errMsg \(mixed\) of encapsed string cannot be cast to string\.$#' + identifier: encapsedStringPart.nonString + count: 3 + path: src/Transport/SwooleTransport.php + + - + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: src/Transport/TcpTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\TcpTransport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/TcpTransport.php + + - + message: '#^Parameter \#2 \$length of function fread expects int\<1, max\>, int given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/TcpTransport.php + + - + message: '#^Parameter \#3 \$optionname of function stream_context_set_option expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/TcpTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\TlsTransport\:\:__construct\(\) has parameter \$tlsOptions with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/TlsTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\TlsTransport\:\:buildSslOptions\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/TlsTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\TlsTransport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/TlsTransport.php + + - + message: '#^Parameter \#2 \$length of function fread expects int\<1, max\>, int given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/TlsTransport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\Transport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/Transport.php + + - + message: '#^Method Utopia\\NATS\\Transport\\WebSocketTransport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Transport/WebSocketTransport.php + + - + message: '#^Parameter \#1 \$codepoint of function chr expects int\<0, 255\>, int given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/WebSocketTransport.php + + - + message: '#^Parameter \#2 \$length of function fread expects int\<1, max\>, int given\.$#' + identifier: argument.type + count: 1 + path: src/Transport/WebSocketTransport.php + + - + message: '#^Parameter \#1 \$input of method Utopia\\NATS\\Tests\\Auth\\NKeyAuthTest\:\:base32Decode\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/Auth/NKeyAuthTest.php + + - + message: '#^Parameter \#1 \$signature of function sodium_crypto_sign_verify_detached expects non\-empty\-string, string given\.$#' + identifier: argument.type + count: 1 + path: tests/Auth/NKeyAuthTest.php + + - + message: '#^Call to an undefined method object\:\:isConnected\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Call to an undefined method object\:\:read\(\)\.$#' + identifier: method.notFound + count: 1 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Call to an undefined method object\:\:write\(\)\.$#' + identifier: method.notFound + count: 2 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Generator expects key type int\<0, max\>, int given\.$#' + identifier: generator.keyType + count: 26 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Parameter \#1 \$servers of class Utopia\\NATS\\ConnectionOptions constructor expects list\\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Parameter \#1 \$stream of function fclose expects resource, resource\|false given\.$#' + identifier: argument.type + count: 1 + path: tests/ConnectionDeathDetectionTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\ConnectionProtocolTest\:\:connect\(\) has parameter \$extra with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/ConnectionProtocolTest.php + + - + message: '#^Parameter \#1 \$servers of class Utopia\\NATS\\ConnectionOptions constructor expects list\\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/ConnectionProtocolTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\ConnectionTlsAndAuthTest\:\:connect\(\) has parameter \$extra with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/ConnectionTlsAndAuthTest.php + + - + message: '#^Parameter \#1 \$servers of class Utopia\\NATS\\ConnectionOptions constructor expects list\\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/ConnectionTlsAndAuthTest.php + + - + message: '#^Offset 1 might not exist on array\{\}\|array\{non\-falsy\-string, numeric\-string\}\.$#' + identifier: offsetAccess.notFound + count: 1 + path: tests/ConsumerFetchLifecycleTest.php + + - + message: '#^Parameter \#1 \$servers of class Utopia\\NATS\\ConnectionOptions constructor expects list\\|string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/ConsumerFetchLifecycleTest.php + + - + message: '#^Cannot call method getData\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/ConsumerPullRequestTest.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 1 + path: tests/ConsumerPullRequestTest.php + + - + message: '#^Cannot call method ack\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/E2E/ConsumerNoWaitTest.php + + - + message: '#^Cannot call method getData\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: tests/E2E/ConsumerNoWaitTest.php + + - + message: '#^Cannot use array destructuring on mixed\.$#' + identifier: offsetAccess.nonArray + count: 1 + path: tests/E2E/ObjectStoreExtraTest.php + + - + message: '#^Cannot use array destructuring on mixed\.$#' + identifier: offsetAccess.nonArray + count: 1 + path: tests/E2E/ObjectStoreTest.php + + - + message: '#^Cannot cast mixed to int\.$#' + identifier: cast.int + count: 1 + path: tests/E2E/OrderedConsumerTest.php + + - + message: '#^Cannot access property \$data on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/E2E/PubSubTest.php + + - + message: '#^Cannot access property \$headers on mixed\.$#' + identifier: property.nonObject + count: 3 + path: tests/E2E/PubSubTest.php + + - + message: '#^Cannot access property \$subject on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/E2E/PubSubTest.php + + - + message: '#^Cannot call method get\(\) on mixed\.$#' + identifier: method.nonObject + count: 2 + path: tests/E2E/PubSubTest.php + + - + message: '#^Cannot access property \$replyTo on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/E2E/RequestManyTest.php + + - + message: '#^Parameter \#1 \$subject of method Utopia\\NATS\\Connection\:\:publish\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/RequestManyTest.php + + - + message: '#^Binary operation "\." between ''echo\: '' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: tests/E2E/RequestReplyTest.php + + - + message: '#^Cannot access property \$data on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/E2E/RequestReplyTest.php + + - + message: '#^Cannot access property \$replyTo on mixed\.$#' + identifier: property.nonObject + count: 2 + path: tests/E2E/RequestReplyTest.php + + - + message: '#^Parameter \#1 \$subject of method Utopia\\NATS\\Connection\:\:publish\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/RequestReplyTest.php + + - + message: '#^Access to an undefined property Throwable\|Utopia\\NATS\\Message\:\:\$data\.$#' + identifier: property.notFound + count: 2 + path: tests/E2E/RequestsTest.php + + - + message: '#^Cannot call method get\(\) on Utopia\\NATS\\Headers\|null\.$#' + identifier: method.nonObject + count: 1 + path: tests/E2E/RequestsTest.php + + - + message: '#^Parameter \#1 \$subject of method Utopia\\NATS\\Connection\:\:publish\(\) expects string, string\|null given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/RequestsTest.php + + - + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 3 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''endpoints'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''env'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''metadata'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''name'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''num_errors'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''num_requests'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''owner'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''queue_group'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''subject'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Cannot access offset ''visibility'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 3 + path: tests/E2E/ServiceExtrasTest.php + + - + message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' + identifier: foreach.nonIterable + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''description'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''endpoints'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''id'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''name'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''num_errors'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''num_requests'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''processing_time'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''type'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/E2E/ServiceTest.php + + - + message: '#^Cannot access offset ''version'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(mixed\)\: mixed\)\|null, Closure\(array\)\: mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Parameter \#2 \$array of function array_map expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Parameter \#2 \$array of method PHPUnit\\Framework\\Assert\:\:assertArrayHasKey\(\) expects array\\|ArrayAccess\<\(int\|string\), covariant mixed\>, mixed given\.$#' + identifier: argument.type + count: 2 + path: tests/E2E/ServiceTest.php + + - + message: '#^Possibly invalid array key type mixed\.$#' + identifier: offsetAccess.invalidOffset + count: 1 + path: tests/E2E/ServiceTest.php + + - + message: '#^Binary operation "\." between ''pong\:'' and mixed results in an error\.$#' + identifier: binaryOp.invalid + count: 1 + path: tests/E2E/WebSocketTest.php + + - + message: '#^Cannot access property \$data on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/E2E/WebSocketTest.php + + - + message: '#^Cannot access property \$replyTo on mixed\.$#' + identifier: property.nonObject + count: 1 + path: tests/E2E/WebSocketTest.php + + - + message: '#^Parameter \#1 \$subject of method Utopia\\NATS\\Connection\:\:publish\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 1 + path: tests/E2E/WebSocketTest.php + + - + message: '#^Cannot access offset ''payload'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/ParserFrameIntegrityTest.php + + - + message: '#^Cannot access offset ''subject'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/ParserFrameIntegrityTest.php + + - + message: '#^Cannot access offset ''headers'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''payload'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 6 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''replyTo'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''server_id'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''sid'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 2 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''subject'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 3 + path: tests/Protocol/ParserTest.php + + - + message: '#^Cannot access offset ''version'' on mixed\.$#' + identifier: offsetAccess.nonOffsetAccessible + count: 1 + path: tests/Protocol/ParserTest.php + + - + message: '#^Method Utopia\\NATS\\Transport\\Transport@anonymous/tests/Protocol/ParserTest\.php\:17\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/Protocol/ParserTest.php + + - + message: '#^Access to an undefined property Throwable\|Utopia\\NATS\\Message\:\:\$data\.$#' + identifier: property.notFound + count: 4 + path: tests/RequestsTest.php + + - + message: '#^Cannot call method isConnected\(\) on Utopia\\NATS\\Connection\|null\.$#' + identifier: method.nonObject + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\RequestsTest\:\:bufferedReplies\(\) return type has no value type specified in iterable type iterable\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\RequestsTest\:\:invalid\(\) return type has no value type specified in iterable type iterable\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\RequestsTest\:\:keepalive\(\) return type has no value type specified in iterable type iterable\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\RequestsTest\:\:reconnect\(\) return type has no value type specified in iterable type iterable\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\RequestsTest\:\:testInvalidInputDoesNotWriteOrInvokeCallbacks\(\) has parameter \$requests with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Offset int\<0, max\>\|null might not exist on list\\.$#' + identifier: offsetAccess.notFound + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Parameter \#1 \$requests of method Utopia\\NATS\\Connection\:\:requestBatch\(\) expects list\, array given\.$#' + identifier: argument.type + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Possibly invalid array key type int\<0, max\>\|null\.$#' + identifier: offsetAccess.invalidOffset + count: 2 + path: tests/RequestsTest.php + + - + message: '#^Trying to invoke Closure\|null but it might not be a callable\.$#' + identifier: callable.nonCallable + count: 1 + path: tests/RequestsTest.php + + - + message: '#^Method Utopia\\NATS\\Tests\\Support\\FakeTransport\:\:connectPayload\(\) should return array\ but returns mixed\.$#' + identifier: return.type + count: 1 + path: tests/Support/FakeTransport.php + + - + message: '#^Method Utopia\\NATS\\Tests\\Support\\FakeTransport\:\:upgradeTls\(\) has parameter \$options with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/Support/FakeTransport.php + + - + message: '#^Property Utopia\\NATS\\Tests\\Support\\FakeTransport\:\:\$tlsUpgrades \(list\\>\) does not accept non\-empty\-list\\.$#' + identifier: assign.propertyType + count: 1 + path: tests/Support/FakeTransport.php diff --git a/packages/nats/phpstan.neon b/packages/nats/phpstan.neon index f3da750a966..bfd97a685a5 100644 --- a/packages/nats/phpstan.neon +++ b/packages/nats/phpstan.neon @@ -1,5 +1,9 @@ includes: - - ../../phpstan.neon + - phpstan-baseline.neon parameters: + level: max treatPhpDocTypesAsCertain: false + paths: + - src + - tests diff --git a/packages/nats/phpunit.xml b/packages/nats/phpunit.xml index 9920c46cf09..bc0ac92eada 100644 --- a/packages/nats/phpunit.xml +++ b/packages/nats/phpunit.xml @@ -1,6 +1,6 @@ - tests/Unit + tests + tests/E2E tests/E2E diff --git a/packages/nats/rector.php b/packages/nats/rector.php new file mode 100644 index 00000000000..827fa23c067 --- /dev/null +++ b/packages/nats/rector.php @@ -0,0 +1,23 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets() + ->withPreparedSets( + typeDeclarations: true, + ) + // Absorbing moves code: keep the public surface (property declarations, + // readonly-ness) exactly as released. + ->withSkip([ + ClassPropertyAssignToConstructorPromotionRector::class, + ReadOnlyPropertyRector::class, + ]); diff --git a/packages/nats/src/Auth/TokenAuth.php b/packages/nats/src/Auth/TokenAuth.php index dd6753df207..3b2bc22237d 100644 --- a/packages/nats/src/Auth/TokenAuth.php +++ b/packages/nats/src/Auth/TokenAuth.php @@ -8,7 +8,8 @@ final class TokenAuth implements Authenticator { public function __construct( private readonly string $token, - ) {} + ) { + } public function authenticate(?string $nonce = null): array { diff --git a/packages/nats/src/Auth/UserPassAuth.php b/packages/nats/src/Auth/UserPassAuth.php index c7f52ad0ce8..682ef51c90e 100644 --- a/packages/nats/src/Auth/UserPassAuth.php +++ b/packages/nats/src/Auth/UserPassAuth.php @@ -9,7 +9,8 @@ final class UserPassAuth implements Authenticator public function __construct( private readonly string $user, private readonly string $pass, - ) {} + ) { + } public function authenticate(?string $nonce = null): array { diff --git a/packages/nats/src/Connection.php b/packages/nats/src/Connection.php index ceca11dc9b7..00abb83f7dd 100644 --- a/packages/nats/src/Connection.php +++ b/packages/nats/src/Connection.php @@ -178,10 +178,10 @@ public function subscribe(string $subject, ?\Closure $callback = null, ?string $ $sid, $subject, $queue, - $callback instanceof \Closure ? fn(Message $message) => $this->notify($callback, $message) : null, + $callback instanceof \Closure ? fn (Message $message) => $this->notify($callback, $message) : null, $this->options->subPendingMsgsLimit, $this->options->subPendingBytesLimit, - $slow instanceof \Closure ? fn(Subscription $subscription) => $this->notify($slow, $subscription) : null, + $slow instanceof \Closure ? fn (Subscription $subscription) => $this->notify($slow, $subscription) : null, ); $sub->setConnection($this); @@ -963,7 +963,7 @@ private function handleLameDuck(): void $others = array_values(array_filter( $this->serverPool, - fn(string $url): bool => $url !== $this->currentServer, + fn (string $url): bool => $url !== $this->currentServer, )); // Only proactively reconnect when a different server is available; diff --git a/packages/nats/src/Exception/AuthenticationException.php b/packages/nats/src/Exception/AuthenticationException.php index 6ce5d6737bc..a171a94f31e 100644 --- a/packages/nats/src/Exception/AuthenticationException.php +++ b/packages/nats/src/Exception/AuthenticationException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class AuthenticationException extends NatsException {} +class AuthenticationException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/ConnectionException.php b/packages/nats/src/Exception/ConnectionException.php index f9b02e2c516..563d266b041 100644 --- a/packages/nats/src/Exception/ConnectionException.php +++ b/packages/nats/src/Exception/ConnectionException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class ConnectionException extends NatsException {} +class ConnectionException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/KeyValueException.php b/packages/nats/src/Exception/KeyValueException.php index 842c6bbfb50..0a83ff8df11 100644 --- a/packages/nats/src/Exception/KeyValueException.php +++ b/packages/nats/src/Exception/KeyValueException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class KeyValueException extends NatsException {} +class KeyValueException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/MaxPayloadException.php b/packages/nats/src/Exception/MaxPayloadException.php index f26f0ff284b..3f79082324e 100644 --- a/packages/nats/src/Exception/MaxPayloadException.php +++ b/packages/nats/src/Exception/MaxPayloadException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class MaxPayloadException extends NatsException {} +class MaxPayloadException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/NatsException.php b/packages/nats/src/Exception/NatsException.php index 7ff43af0aab..af532e1033c 100644 --- a/packages/nats/src/Exception/NatsException.php +++ b/packages/nats/src/Exception/NatsException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class NatsException extends \RuntimeException {} +class NatsException extends \RuntimeException +{ +} diff --git a/packages/nats/src/Exception/ObjectStoreException.php b/packages/nats/src/Exception/ObjectStoreException.php index 2816f68d18b..425ef2bf75e 100644 --- a/packages/nats/src/Exception/ObjectStoreException.php +++ b/packages/nats/src/Exception/ObjectStoreException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class ObjectStoreException extends NatsException {} +class ObjectStoreException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/PermissionException.php b/packages/nats/src/Exception/PermissionException.php index 6252691b724..88f8df1b593 100644 --- a/packages/nats/src/Exception/PermissionException.php +++ b/packages/nats/src/Exception/PermissionException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class PermissionException extends NatsException {} +class PermissionException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/ProtocolException.php b/packages/nats/src/Exception/ProtocolException.php index 0f52ece1418..2719f5bf216 100644 --- a/packages/nats/src/Exception/ProtocolException.php +++ b/packages/nats/src/Exception/ProtocolException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class ProtocolException extends NatsException {} +class ProtocolException extends NatsException +{ +} diff --git a/packages/nats/src/Exception/TimeoutException.php b/packages/nats/src/Exception/TimeoutException.php index 0dcb1ef472f..6ab0a46b0ba 100644 --- a/packages/nats/src/Exception/TimeoutException.php +++ b/packages/nats/src/Exception/TimeoutException.php @@ -4,4 +4,6 @@ namespace Utopia\NATS\Exception; -class TimeoutException extends ConnectionException {} +class TimeoutException extends ConnectionException +{ +} diff --git a/packages/nats/src/JetStream/AccountInfo.php b/packages/nats/src/JetStream/AccountInfo.php index 41f7603ee62..a67871ca770 100644 --- a/packages/nats/src/JetStream/AccountInfo.php +++ b/packages/nats/src/JetStream/AccountInfo.php @@ -20,7 +20,8 @@ public function __construct( public readonly int $apiErrors, public readonly ?string $domain = null, public readonly array $raw = [], - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/ApiError.php b/packages/nats/src/JetStream/ApiError.php index f70d9f19950..a34d0a89931 100644 --- a/packages/nats/src/JetStream/ApiError.php +++ b/packages/nats/src/JetStream/ApiError.php @@ -10,7 +10,8 @@ public function __construct( public readonly int $code, public readonly int $errCode, public readonly string $description, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/Consumer.php b/packages/nats/src/JetStream/Consumer.php index 01c821d84d7..955728370ee 100644 --- a/packages/nats/src/JetStream/Consumer.php +++ b/packages/nats/src/JetStream/Consumer.php @@ -22,7 +22,8 @@ public function __construct( private readonly string $stream, private ConsumerInfo $info, private readonly string $apiPrefix = '$JS.API', - ) {} + ) { + } /** * Fetch a batch of messages from the consumer. diff --git a/packages/nats/src/JetStream/ConsumerConfig.php b/packages/nats/src/JetStream/ConsumerConfig.php index d040646df2f..39342bfdf9f 100644 --- a/packages/nats/src/JetStream/ConsumerConfig.php +++ b/packages/nats/src/JetStream/ConsumerConfig.php @@ -43,7 +43,8 @@ public function __construct( public readonly ?float $idleHeartbeat = null, public readonly ?array $metadata = null, public readonly ?array $backoff = null, - ) {} + ) { + } public function toArray(): array { diff --git a/packages/nats/src/JetStream/ConsumerInfo.php b/packages/nats/src/JetStream/ConsumerInfo.php index 8bf83ce6d67..fbaf5cdeead 100644 --- a/packages/nats/src/JetStream/ConsumerInfo.php +++ b/packages/nats/src/JetStream/ConsumerInfo.php @@ -23,7 +23,8 @@ public function __construct( public readonly bool $pushBound = false, public readonly ?string $cluster = null, public readonly ?array $metadata = null, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/ConsumerLimits.php b/packages/nats/src/JetStream/ConsumerLimits.php index e977aece603..b24bdd74752 100644 --- a/packages/nats/src/JetStream/ConsumerLimits.php +++ b/packages/nats/src/JetStream/ConsumerLimits.php @@ -12,7 +12,8 @@ final class ConsumerLimits public function __construct( public readonly ?float $inactiveThreshold = null, public readonly ?int $maxAckPending = null, - ) {} + ) { + } public function toArray(): array { diff --git a/packages/nats/src/JetStream/ExternalStream.php b/packages/nats/src/JetStream/ExternalStream.php index d996f7784d2..211fadf6470 100644 --- a/packages/nats/src/JetStream/ExternalStream.php +++ b/packages/nats/src/JetStream/ExternalStream.php @@ -9,7 +9,8 @@ final class ExternalStream public function __construct( public readonly string $api, public readonly ?string $deliver = null, - ) {} + ) { + } /** * @return array diff --git a/packages/nats/src/JetStream/JetStream.php b/packages/nats/src/JetStream/JetStream.php index 2dd2b19d7e6..781c1195cd8 100644 --- a/packages/nats/src/JetStream/JetStream.php +++ b/packages/nats/src/JetStream/JetStream.php @@ -49,7 +49,7 @@ public function __construct( */ public function ackBatch(array $messages, \Closure $confirmed, ?float $timeout = null): void { - $requests = array_map(static fn(JetStreamMessage $message): Request => new Request( + $requests = array_map(static fn (JetStreamMessage $message): Request => new Request( subject: $message->message->replyTo ?? throw new \RuntimeException('Cannot acknowledge: message has no reply subject'), ), $messages); $this->conn->requestBatch($requests, static function (int $index, Message|\Throwable $result) use ($confirmed): void { diff --git a/packages/nats/src/JetStream/JetStreamMessage.php b/packages/nats/src/JetStream/JetStreamMessage.php index 6d82264e88a..3ddcdfa50a4 100644 --- a/packages/nats/src/JetStream/JetStreamMessage.php +++ b/packages/nats/src/JetStream/JetStreamMessage.php @@ -15,7 +15,8 @@ final class JetStreamMessage public function __construct( private readonly Connection $conn, public readonly Message $message, - ) {} + ) { + } public function ack(): void { diff --git a/packages/nats/src/JetStream/MessageBatch.php b/packages/nats/src/JetStream/MessageBatch.php index 0f37bfec92b..64fa2f8d31f 100644 --- a/packages/nats/src/JetStream/MessageBatch.php +++ b/packages/nats/src/JetStream/MessageBatch.php @@ -14,7 +14,8 @@ final class MessageBatch implements \IteratorAggregate, \Countable public function __construct( private readonly Connection $conn, - ) {} + ) { + } public function addMessage(Message $msg): void { diff --git a/packages/nats/src/JetStream/MsgMetadata.php b/packages/nats/src/JetStream/MsgMetadata.php index 5d13bd99e94..2f026a69a27 100644 --- a/packages/nats/src/JetStream/MsgMetadata.php +++ b/packages/nats/src/JetStream/MsgMetadata.php @@ -15,7 +15,8 @@ public function __construct( public readonly string $timestamp, public readonly int $numPending, public readonly ?string $domain = null, - ) {} + ) { + } /** * Parse from reply subject: $JS.ACK....... diff --git a/packages/nats/src/JetStream/Placement.php b/packages/nats/src/JetStream/Placement.php index 103e336d882..09962dafce9 100644 --- a/packages/nats/src/JetStream/Placement.php +++ b/packages/nats/src/JetStream/Placement.php @@ -12,7 +12,8 @@ final class Placement public function __construct( public readonly ?string $cluster = null, public readonly ?array $tags = null, - ) {} + ) { + } public function toArray(): array { diff --git a/packages/nats/src/JetStream/PubAck.php b/packages/nats/src/JetStream/PubAck.php index 1ec5dca0d00..54c7c2add41 100644 --- a/packages/nats/src/JetStream/PubAck.php +++ b/packages/nats/src/JetStream/PubAck.php @@ -11,7 +11,8 @@ public function __construct( public readonly int $sequence, public readonly ?string $domain = null, public readonly bool $duplicate = false, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/Republish.php b/packages/nats/src/JetStream/Republish.php index fbc07b7c617..5d8beecff54 100644 --- a/packages/nats/src/JetStream/Republish.php +++ b/packages/nats/src/JetStream/Republish.php @@ -10,7 +10,8 @@ public function __construct( public readonly string $source, public readonly string $destination, public readonly bool $headersOnly = false, - ) {} + ) { + } public function toArray(): array { diff --git a/packages/nats/src/JetStream/SequenceInfo.php b/packages/nats/src/JetStream/SequenceInfo.php index d0969fb3dec..fc67a9748bb 100644 --- a/packages/nats/src/JetStream/SequenceInfo.php +++ b/packages/nats/src/JetStream/SequenceInfo.php @@ -10,7 +10,8 @@ public function __construct( public readonly int $consumerSeq, public readonly int $streamSeq, public readonly ?string $lastActive = null, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/Stream.php b/packages/nats/src/JetStream/Stream.php index 765faa755ee..5128bb0946f 100644 --- a/packages/nats/src/JetStream/Stream.php +++ b/packages/nats/src/JetStream/Stream.php @@ -9,7 +9,8 @@ final class Stream public function __construct( private readonly JetStream $js, private StreamInfo $info, - ) {} + ) { + } public function getName(): string { diff --git a/packages/nats/src/JetStream/StreamConfig.php b/packages/nats/src/JetStream/StreamConfig.php index 3db095b072f..c2436d69d94 100644 --- a/packages/nats/src/JetStream/StreamConfig.php +++ b/packages/nats/src/JetStream/StreamConfig.php @@ -48,7 +48,8 @@ public function __construct( public readonly ?ConsumerLimits $consumerLimits = null, public readonly bool $allowMsgTtl = false, public readonly ?float $subjectDeleteMarkerTtl = null, - ) {} + ) { + } public function toArray(): array { @@ -94,7 +95,7 @@ public function toArray(): array } if ($this->sources !== null) { $data['sources'] = array_map( - static fn(StreamSource $s): array => $s->toArray(), + static fn (StreamSource $s): array => $s->toArray(), $this->sources, ); } diff --git a/packages/nats/src/JetStream/StreamInfo.php b/packages/nats/src/JetStream/StreamInfo.php index cbb87ef3829..043ec24bce8 100644 --- a/packages/nats/src/JetStream/StreamInfo.php +++ b/packages/nats/src/JetStream/StreamInfo.php @@ -10,7 +10,8 @@ public function __construct( public readonly StreamConfig $config, public readonly StreamState $state, public readonly string $created, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/StreamMessage.php b/packages/nats/src/JetStream/StreamMessage.php index 600ff42458a..89049219aa9 100644 --- a/packages/nats/src/JetStream/StreamMessage.php +++ b/packages/nats/src/JetStream/StreamMessage.php @@ -14,7 +14,8 @@ public function __construct( public readonly string $data, public readonly ?string $time = null, public readonly ?Headers $headers = null, - ) {} + ) { + } /** * Parse the `message` object returned by $JS.API.STREAM.MSG.GET. diff --git a/packages/nats/src/JetStream/StreamSource.php b/packages/nats/src/JetStream/StreamSource.php index 208453252b6..2cc0a785a0a 100644 --- a/packages/nats/src/JetStream/StreamSource.php +++ b/packages/nats/src/JetStream/StreamSource.php @@ -20,7 +20,8 @@ public function __construct( public readonly ?string $filterSubject = null, public readonly ?array $subjectTransforms = null, public readonly ?ExternalStream $external = null, - ) {} + ) { + } public function toArray(): array { @@ -37,7 +38,7 @@ public function toArray(): array } if ($this->subjectTransforms !== null) { $data['subject_transforms'] = array_map( - static fn(SubjectTransform $t): array => $t->toArray(), + static fn (SubjectTransform $t): array => $t->toArray(), $this->subjectTransforms, ); } diff --git a/packages/nats/src/JetStream/StreamState.php b/packages/nats/src/JetStream/StreamState.php index 2f51e48aa00..abbcc9ba88d 100644 --- a/packages/nats/src/JetStream/StreamState.php +++ b/packages/nats/src/JetStream/StreamState.php @@ -16,7 +16,8 @@ public function __construct( public readonly int $consumerCount, public readonly int $numDeleted = 0, public readonly int $numSubjects = 0, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/JetStream/SubjectTransform.php b/packages/nats/src/JetStream/SubjectTransform.php index 9901862bd77..6ceaf0f9c68 100644 --- a/packages/nats/src/JetStream/SubjectTransform.php +++ b/packages/nats/src/JetStream/SubjectTransform.php @@ -9,7 +9,8 @@ final class SubjectTransform public function __construct( public readonly string $source, public readonly string $destination, - ) {} + ) { + } /** * @return array diff --git a/packages/nats/src/KeyValue/KeyValue.php b/packages/nats/src/KeyValue/KeyValue.php index 68c738e2db0..5f099d2f245 100644 --- a/packages/nats/src/KeyValue/KeyValue.php +++ b/packages/nats/src/KeyValue/KeyValue.php @@ -23,7 +23,8 @@ public function __construct( private readonly Connection $conn, private readonly JetStream $js, private readonly string $bucket, - ) {} + ) { + } public function get(string $key): KeyValueEntry { diff --git a/packages/nats/src/KeyValue/KeyValueConfig.php b/packages/nats/src/KeyValue/KeyValueConfig.php index 68c4d737275..2edb3e29cef 100644 --- a/packages/nats/src/KeyValue/KeyValueConfig.php +++ b/packages/nats/src/KeyValue/KeyValueConfig.php @@ -23,7 +23,8 @@ public function __construct( public readonly int $maxBytes = -1, public readonly StorageType $storage = StorageType::File, public readonly int $replicas = 1, - ) {} + ) { + } public function toStreamConfig(): StreamConfig { diff --git a/packages/nats/src/KeyValue/KeyValueEntry.php b/packages/nats/src/KeyValue/KeyValueEntry.php index c5f518e86d9..26f04f608fa 100644 --- a/packages/nats/src/KeyValue/KeyValueEntry.php +++ b/packages/nats/src/KeyValue/KeyValueEntry.php @@ -13,5 +13,6 @@ public function __construct( public readonly int $revision, public readonly ?string $created = null, public readonly KeyValueOperation $operation = KeyValueOperation::Put, - ) {} + ) { + } } diff --git a/packages/nats/src/KeyValue/KeyValueStatus.php b/packages/nats/src/KeyValue/KeyValueStatus.php index a8d5bf3b30d..24ab787608b 100644 --- a/packages/nats/src/KeyValue/KeyValueStatus.php +++ b/packages/nats/src/KeyValue/KeyValueStatus.php @@ -15,5 +15,6 @@ public function __construct( public readonly int $history, public readonly ?float $ttl, public readonly StreamInfo $streamInfo, - ) {} + ) { + } } diff --git a/packages/nats/src/KeyValue/KeyValueWatchOptions.php b/packages/nats/src/KeyValue/KeyValueWatchOptions.php index cbcf41ebe51..ff41bd70b76 100644 --- a/packages/nats/src/KeyValue/KeyValueWatchOptions.php +++ b/packages/nats/src/KeyValue/KeyValueWatchOptions.php @@ -21,5 +21,6 @@ public function __construct( public readonly bool $updatesOnly = false, public readonly bool $ignoreDeletes = false, public readonly bool $metaOnly = false, - ) {} + ) { + } } diff --git a/packages/nats/src/Message.php b/packages/nats/src/Message.php index 187c87c0f06..d0b1fb11097 100644 --- a/packages/nats/src/Message.php +++ b/packages/nats/src/Message.php @@ -12,5 +12,6 @@ public function __construct( public readonly ?string $replyTo = null, public readonly ?Headers $headers = null, public readonly ?string $sid = null, - ) {} + ) { + } } diff --git a/packages/nats/src/ObjectStore/ObjectLink.php b/packages/nats/src/ObjectStore/ObjectLink.php index 22c01628519..e0a603e53c6 100644 --- a/packages/nats/src/ObjectStore/ObjectLink.php +++ b/packages/nats/src/ObjectStore/ObjectLink.php @@ -13,7 +13,8 @@ final class ObjectLink public function __construct( public readonly string $bucket, public readonly ?string $name = null, - ) {} + ) { + } /** * @return array diff --git a/packages/nats/src/ObjectStore/ObjectMeta.php b/packages/nats/src/ObjectStore/ObjectMeta.php index 653d3fc2db1..43b2ddba6b0 100644 --- a/packages/nats/src/ObjectStore/ObjectMeta.php +++ b/packages/nats/src/ObjectStore/ObjectMeta.php @@ -21,7 +21,8 @@ public function __construct( public readonly bool $deleted = false, public readonly ?array $metadata = null, public readonly ?ObjectLink $link = null, - ) {} + ) { + } /** * @return array @@ -63,7 +64,7 @@ public static function fromArray(array $data): self { $metadata = null; if (isset($data['metadata']) && \is_array($data['metadata'])) { - $metadata = array_map(static fn(mixed $v): string => (string) $v, $data['metadata']); + $metadata = array_map(static fn (mixed $v): string => (string) $v, $data['metadata']); } $link = null; diff --git a/packages/nats/src/ObjectStore/ObjectStore.php b/packages/nats/src/ObjectStore/ObjectStore.php index f1ea229f260..28dd457daf1 100644 --- a/packages/nats/src/ObjectStore/ObjectStore.php +++ b/packages/nats/src/ObjectStore/ObjectStore.php @@ -24,7 +24,8 @@ public function __construct( private readonly Connection $conn, private readonly JetStream $js, private readonly string $bucket, - ) {} + ) { + } /** * Create (or update) the backing stream and return a store handle. @@ -377,7 +378,7 @@ public function seal(): void // Empty JSON objects in the info response (e.g. consumer_limits) decode to // empty PHP arrays and would re-encode as [] — which the API rejects as // invalid JSON for a struct field. Drop them; the server re-applies defaults. - $config = array_filter($config, static fn(mixed $v): bool => $v !== []); + $config = array_filter($config, static fn (mixed $v): bool => $v !== []); $config['sealed'] = true; $updateResponse = $this->conn->request( diff --git a/packages/nats/src/ObjectStore/ObjectStoreConfig.php b/packages/nats/src/ObjectStore/ObjectStoreConfig.php index aad1747b7c3..0d1a5abc1ed 100644 --- a/packages/nats/src/ObjectStore/ObjectStoreConfig.php +++ b/packages/nats/src/ObjectStore/ObjectStoreConfig.php @@ -21,7 +21,8 @@ public function __construct( public readonly ?float $ttl = null, public readonly StorageType $storage = StorageType::File, public readonly int $replicas = 1, - ) {} + ) { + } public function toStreamConfig(): StreamConfig { diff --git a/packages/nats/src/Protocol/Parser.php b/packages/nats/src/Protocol/Parser.php index 7a8576ef43b..f92d603e282 100644 --- a/packages/nats/src/Protocol/Parser.php +++ b/packages/nats/src/Protocol/Parser.php @@ -15,7 +15,8 @@ final class Parser public function __construct( private readonly Transport $transport, - ) {} + ) { + } /** Whether received bytes remain to be parsed before another transport read. */ public function hasBufferedData(): bool @@ -73,12 +74,12 @@ public function next(?float $timeout = null): array // MSG [reply-to] <#bytes> if (str_starts_with($line, 'MSG ')) { - return $this->readFrame(fn(): array => $this->parseMsg(substr($line, 4), $timeout)); + return $this->readFrame(fn (): array => $this->parseMsg(substr($line, 4), $timeout)); } // HMSG [reply-to] <#header-bytes> <#total-bytes> if (str_starts_with($line, 'HMSG ')) { - return $this->readFrame(fn(): array => $this->parseHmsg(substr($line, 5), $timeout)); + return $this->readFrame(fn (): array => $this->parseHmsg(substr($line, 5), $timeout)); } throw new ProtocolException("Unknown protocol operation: {$line}"); diff --git a/packages/nats/src/ServerInfo.php b/packages/nats/src/ServerInfo.php index bc151b40f72..f591e5486ad 100644 --- a/packages/nats/src/ServerInfo.php +++ b/packages/nats/src/ServerInfo.php @@ -23,7 +23,8 @@ public function __construct( public readonly bool $jetstream, public readonly ?int $clientId, public readonly ?string $clientIp, - ) {} + ) { + } public static function fromArray(array $data): self { diff --git a/packages/nats/src/Services/Group.php b/packages/nats/src/Services/Group.php index 82467df3bad..00b58882633 100644 --- a/packages/nats/src/Services/Group.php +++ b/packages/nats/src/Services/Group.php @@ -17,7 +17,8 @@ public function __construct( private readonly Service $service, private readonly string $prefix, private readonly ?string $queueGroup = null, - ) {} + ) { + } /** * Create a nested group. Its subject prefix is this group's prefix diff --git a/packages/nats/src/Services/Service.php b/packages/nats/src/Services/Service.php index 24bf8a9f458..3c87adca90a 100644 --- a/packages/nats/src/Services/Service.php +++ b/packages/nats/src/Services/Service.php @@ -109,9 +109,9 @@ public function start(): self $this->subscribeEndpoint($endpoint); } - $this->subscribeDiscovery('PING', fn(Message $msg) => $this->reply($msg, $this->pingResponse())); - $this->subscribeDiscovery('INFO', fn(Message $msg) => $this->reply($msg, $this->infoResponse())); - $this->subscribeDiscovery('STATS', fn(Message $msg) => $this->reply($msg, $this->statsResponse())); + $this->subscribeDiscovery('PING', fn (Message $msg) => $this->reply($msg, $this->pingResponse())); + $this->subscribeDiscovery('INFO', fn (Message $msg) => $this->reply($msg, $this->infoResponse())); + $this->subscribeDiscovery('STATS', fn (Message $msg) => $this->reply($msg, $this->statsResponse())); return $this; } @@ -224,7 +224,7 @@ private function infoResponse(): string 'version' => $this->version, 'description' => $this->description, 'metadata' => $this->metadataObject(), - 'endpoints' => array_map(fn(Endpoint $e): array => $e->info(), array_values($this->endpoints)), + 'endpoints' => array_map(fn (Endpoint $e): array => $e->info(), array_values($this->endpoints)), ]); } @@ -237,7 +237,7 @@ private function statsResponse(): string 'version' => $this->version, 'started' => $this->started, 'metadata' => $this->metadataObject(), - 'endpoints' => array_map(fn(Endpoint $e): array => $e->stats(), array_values($this->endpoints)), + 'endpoints' => array_map(fn (Endpoint $e): array => $e->stats(), array_values($this->endpoints)), ]); } diff --git a/packages/nats/src/Transport/SwooleTransport.php b/packages/nats/src/Transport/SwooleTransport.php index 100b2e72469..0454143f907 100644 --- a/packages/nats/src/Transport/SwooleTransport.php +++ b/packages/nats/src/Transport/SwooleTransport.php @@ -29,7 +29,8 @@ final class SwooleTransport implements Transport public function __construct( private readonly bool $secure = false, private readonly array $tlsOptions = [], - ) {} + ) { + } public function connect(string $host, int $port, float $timeout): void { diff --git a/packages/nats/src/Transport/TlsTransport.php b/packages/nats/src/Transport/TlsTransport.php index b1735bf5653..c9ee802a19d 100644 --- a/packages/nats/src/Transport/TlsTransport.php +++ b/packages/nats/src/Transport/TlsTransport.php @@ -14,7 +14,8 @@ final class TlsTransport implements Transport public function __construct( private readonly array $tlsOptions = [], - ) {} + ) { + } public function connect(string $host, int $port, float $timeout): void { diff --git a/packages/nats/src/Transport/WebSocketTransport.php b/packages/nats/src/Transport/WebSocketTransport.php index 00176069bbd..f161561268b 100644 --- a/packages/nats/src/Transport/WebSocketTransport.php +++ b/packages/nats/src/Transport/WebSocketTransport.php @@ -31,7 +31,8 @@ public function __construct( private readonly bool $secure = false, private readonly array $tlsOptions = [], private readonly string $path = '/', - ) {} + ) { + } public function connect(string $host, int $port, float $timeout): void { diff --git a/packages/nats/tests/Unit/Auth/NKeyAuthTest.php b/packages/nats/tests/Auth/NKeyAuthTest.php similarity index 99% rename from packages/nats/tests/Unit/Auth/NKeyAuthTest.php rename to packages/nats/tests/Auth/NKeyAuthTest.php index dc4e7cfa1bf..67cb2dabc73 100644 --- a/packages/nats/tests/Unit/Auth/NKeyAuthTest.php +++ b/packages/nats/tests/Auth/NKeyAuthTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Auth; +namespace Utopia\NATS\Tests\Auth; use PHPUnit\Framework\TestCase; use Utopia\NATS\Auth\NKeyAuth; diff --git a/packages/nats/tests/Unit/ConnectionDeathDetectionTest.php b/packages/nats/tests/ConnectionDeathDetectionTest.php similarity index 97% rename from packages/nats/tests/Unit/ConnectionDeathDetectionTest.php rename to packages/nats/tests/ConnectionDeathDetectionTest.php index fbf89854e02..d6584f32070 100644 --- a/packages/nats/tests/Unit/ConnectionDeathDetectionTest.php +++ b/packages/nats/tests/ConnectionDeathDetectionTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -12,7 +12,7 @@ use Utopia\NATS\Exception\ConnectionException; use Utopia\NATS\Exception\PermissionException; use Utopia\NATS\Exception\ProtocolException; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; use Utopia\NATS\Transport\TcpTransport; use Utopia\NATS\Transport\TlsTransport; use Utopia\NATS\Transport\WebSocketTransport; @@ -43,7 +43,7 @@ private function connect(FakeTransport $fake, array $extra = []): Connection { return Connection::connect(new ConnectionOptions(...array_merge([ 'servers' => 'nats://127.0.0.1:4222', - 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + 'transportFactory' => fn (string $scheme): FakeTransport => $fake, // Keep reconnects instant and free of jitter so the assertions below // are about behaviour rather than timing. 'reconnectWait' => 0.0, @@ -251,7 +251,7 @@ public function testTickRecyclesAConnectionThatStoppedAnsweringPings(): void // Counted off the wire rather than from a callback: a recycled // connection re-runs the handshake, so a fresh CONNECT is the effect // worth asserting on. - $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $handshakes = fn (): int => substr_count($fake->written, 'CONNECT '); $this->assertSame(1, $handshakes()); // The handshake needed its PONG; from here the server goes silent. @@ -304,7 +304,7 @@ public function testPublishingDoesNotAccumulateUnansweredPings(): void 'maxPingsOut' => 1, ]); - $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $handshakes = fn (): int => substr_count($fake->written, 'CONNECT '); $this->assertSame(1, $handshakes()); // Nothing will answer from here, exactly as on a connection whose @@ -342,7 +342,7 @@ public function testTickCollectsItsOwnPongsSoAHealthyConnectionSurvives(): void 'maxPingsOut' => 2, ]); - $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $handshakes = fn (): int => substr_count($fake->written, 'CONNECT '); // checkPings() only writes. Nothing else clears the outstanding count // on a connection whose holder never reads -- a pooled publisher -- so @@ -393,7 +393,7 @@ public function testPublishTakesTheKeepaliveVerdictBeforeWriting(): void 'maxPingsOut' => 1, ]); - $handshakes = fn(): int => substr_count($fake->written, 'CONNECT '); + $handshakes = fn (): int => substr_count($fake->written, 'CONNECT '); // Spend the budget from the read side, which is where it is spent in // practice: a PING the now-silent server will never answer. diff --git a/packages/nats/tests/Unit/ConnectionExtrasTest.php b/packages/nats/tests/ConnectionExtrasTest.php similarity index 93% rename from packages/nats/tests/Unit/ConnectionExtrasTest.php rename to packages/nats/tests/ConnectionExtrasTest.php index e866fedc62f..d0ad2ced286 100644 --- a/packages/nats/tests/Unit/ConnectionExtrasTest.php +++ b/packages/nats/tests/ConnectionExtrasTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; @@ -11,7 +11,7 @@ use Utopia\NATS\Exception\MaxPayloadException; use Utopia\NATS\Exception\PermissionException; use Utopia\NATS\Exception\ProtocolException; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * Connection-level parity extras: the ADR-7 server-error mapping and the ADR-5 @@ -76,7 +76,7 @@ public function testLameDuckInfoInvokesCallback(): void onLameDuck: function () use (&$fired): void { $fired = true; }, - transportFactory: fn(string $scheme): FakeTransport => $fake, + transportFactory: fn (string $scheme): FakeTransport => $fake, )); // Server signals lame-duck mode via an asynchronous INFO. With only one @@ -100,7 +100,7 @@ public function testNonLameDuckInfoDoesNotInvokeCallback(): void onLameDuck: function () use (&$fired): void { $fired = true; }, - transportFactory: fn(string $scheme): FakeTransport => $fake, + transportFactory: fn (string $scheme): FakeTransport => $fake, )); $fake->pushInbound('INFO {"server_id":"FAKE","ldm":false}' . "\r\n"); diff --git a/packages/nats/tests/Unit/ConnectionProtocolTest.php b/packages/nats/tests/ConnectionProtocolTest.php similarity index 95% rename from packages/nats/tests/Unit/ConnectionProtocolTest.php rename to packages/nats/tests/ConnectionProtocolTest.php index 8ad3727b1b1..faa0269395c 100644 --- a/packages/nats/tests/Unit/ConnectionProtocolTest.php +++ b/packages/nats/tests/ConnectionProtocolTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; @@ -10,7 +10,7 @@ use Utopia\NATS\Exception\MaxPayloadException; use Utopia\NATS\Exception\ProtocolException; use Utopia\NATS\Headers; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * Protocol-level behaviour driven through an in-memory transport: header @@ -22,7 +22,7 @@ private function connect(FakeTransport $fake, array $extra = []): Connection { $args = array_merge([ 'servers' => 'nats://127.0.0.1:4222', - 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + 'transportFactory' => fn (string $scheme): FakeTransport => $fake, ], $extra); return Connection::connect(new ConnectionOptions(...$args)); diff --git a/packages/nats/tests/Unit/ConnectionTlsAndAuthTest.php b/packages/nats/tests/ConnectionTlsAndAuthTest.php similarity index 92% rename from packages/nats/tests/Unit/ConnectionTlsAndAuthTest.php rename to packages/nats/tests/ConnectionTlsAndAuthTest.php index d13a2340a5a..0be5769b400 100644 --- a/packages/nats/tests/Unit/ConnectionTlsAndAuthTest.php +++ b/packages/nats/tests/ConnectionTlsAndAuthTest.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; use Utopia\NATS\ConnectionOptions; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; use Utopia\NATS\Transport\TlsTransport; /** @@ -20,7 +20,7 @@ private function connect(FakeTransport $fake, array $extra = []): Connection { $args = array_merge([ 'servers' => 'nats://127.0.0.1:4222', - 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + 'transportFactory' => fn (string $scheme): FakeTransport => $fake, ], $extra); return Connection::connect(new ConnectionOptions(...$args)); @@ -90,7 +90,7 @@ public function testJwtProviderResolvedAtConnect(): void { $fake = new FakeTransport(); $conn = $this->connect($fake, [ - 'jwtProvider' => fn(): string => 'my.jwt.token', + 'jwtProvider' => fn (): string => 'my.jwt.token', ]); $this->assertSame('my.jwt.token', $fake->connectPayload()['jwt']); diff --git a/packages/nats/tests/Unit/ConsumerFetchLifecycleTest.php b/packages/nats/tests/ConsumerFetchLifecycleTest.php similarity index 96% rename from packages/nats/tests/Unit/ConsumerFetchLifecycleTest.php rename to packages/nats/tests/ConsumerFetchLifecycleTest.php index e791ddd2308..ccec0109b94 100644 --- a/packages/nats/tests/Unit/ConsumerFetchLifecycleTest.php +++ b/packages/nats/tests/ConsumerFetchLifecycleTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; @@ -11,7 +11,7 @@ use Utopia\NATS\JetStream\Consumer; use Utopia\NATS\JetStream\ConsumerInfo; use Utopia\NATS\JetStream\StreamConfig; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * Consumer::fetch() opens an inbox subscription per pull request. It has to be @@ -27,7 +27,7 @@ private function connect(FakeTransport $fake, array $extra = []): Connection return Connection::connect(new ConnectionOptions(...array_merge([ 'servers' => 'nats://127.0.0.1:4222', 'allowReconnect' => false, - 'transportFactory' => fn(string $scheme): FakeTransport => $fake, + 'transportFactory' => fn (string $scheme): FakeTransport => $fake, ], $extra))); } diff --git a/packages/nats/tests/Unit/ConsumerPullRequestTest.php b/packages/nats/tests/ConsumerPullRequestTest.php similarity index 97% rename from packages/nats/tests/Unit/ConsumerPullRequestTest.php rename to packages/nats/tests/ConsumerPullRequestTest.php index 05af1d11701..62f7c614dfb 100644 --- a/packages/nats/tests/Unit/ConsumerPullRequestTest.php +++ b/packages/nats/tests/ConsumerPullRequestTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; use Utopia\NATS\ConnectionOptions; use Utopia\NATS\JetStream\Consumer; use Utopia\NATS\JetStream\ConsumerInfo; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * What Consumer::fetch() puts on the wire, and what it accepts back. @@ -26,7 +26,7 @@ private function connect(FakeTransport $fake): Connection return Connection::connect(new ConnectionOptions( servers: 'nats://127.0.0.1:4222', allowReconnect: false, - transportFactory: fn(string $scheme): FakeTransport => $fake, + transportFactory: fn (string $scheme): FakeTransport => $fake, )); } diff --git a/packages/nats/tests/E2E/JetStreamParityTest.php b/packages/nats/tests/E2E/JetStreamParityTest.php index 8be7d666390..c554a3c02ed 100644 --- a/packages/nats/tests/E2E/JetStreamParityTest.php +++ b/packages/nats/tests/E2E/JetStreamParityTest.php @@ -193,7 +193,7 @@ public function testGetConsumersReturnsTypedInfos(): void $this->assertSame($name, $c->streamName); } - $names = array_map(static fn(ConsumerInfo $c): string => $c->name, $consumers); + $names = array_map(static fn (ConsumerInfo $c): string => $c->name, $consumers); sort($names); $this->assertSame(["one_{$id}", "two_{$id}"], $names); } diff --git a/packages/nats/tests/E2E/KeyValueWatchOptionsTest.php b/packages/nats/tests/E2E/KeyValueWatchOptionsTest.php index 76bf6d43c27..0b451c6e967 100644 --- a/packages/nats/tests/E2E/KeyValueWatchOptionsTest.php +++ b/packages/nats/tests/E2E/KeyValueWatchOptionsTest.php @@ -82,7 +82,7 @@ function () use (&$initDone, &$received, &$countAtInit): void { $this->assertTrue($initDone, 'onInitDone should fire after historical replay'); $this->assertSame(4, $countAtInit, 'all four historical entries delivered before init-done'); - $values = array_map(static fn(KeyValueEntry $e): string => $e->value, \array_slice($received, 0, 3)); + $values = array_map(static fn (KeyValueEntry $e): string => $e->value, \array_slice($received, 0, 3)); $this->assertSame(['v1', 'v2', 'v3'], $values); $this->assertSame(KeyValueOperation::Delete, $received[3]->operation); diff --git a/packages/nats/tests/E2E/ObjectStoreExtraTest.php b/packages/nats/tests/E2E/ObjectStoreExtraTest.php index 542d3e6d2c4..6dd2e21d709 100644 --- a/packages/nats/tests/E2E/ObjectStoreExtraTest.php +++ b/packages/nats/tests/E2E/ObjectStoreExtraTest.php @@ -52,14 +52,14 @@ public function testWatchFiresOnPutAndDelete(): void $this->conn->flush(); $this->store->put('watched.txt', 'hello'); - $this->pumpUntil(fn(): bool => $events !== []); + $this->pumpUntil(fn (): bool => $events !== []); $this->assertCount(1, $events); $this->assertSame('watched.txt', $events[0]->name); $this->assertFalse($events[0]->deleted); $this->store->delete('watched.txt'); - $this->pumpUntil(fn(): bool => \count($events) >= 2); + $this->pumpUntil(fn (): bool => \count($events) >= 2); $sub->unsubscribe(); @@ -79,7 +79,7 @@ public function testWatchIncludeHistoryDeliversCurrentMetaFirst(): void }, includeHistory: true); $this->conn->flush(); - $this->pumpUntil(fn(): bool => $events !== []); + $this->pumpUntil(fn (): bool => $events !== []); $sub->unsubscribe(); diff --git a/packages/nats/tests/E2E/ObjectStoreTest.php b/packages/nats/tests/E2E/ObjectStoreTest.php index ee4e7efa582..2f18042d137 100644 --- a/packages/nats/tests/E2E/ObjectStoreTest.php +++ b/packages/nats/tests/E2E/ObjectStoreTest.php @@ -70,7 +70,7 @@ public function testListReturnsStoredObjects(): void $this->store->put('a.txt', 'aaa'); $this->store->put('b.txt', 'bbb'); - $names = array_map(fn(\Utopia\NATS\ObjectStore\ObjectMeta $m): string => $m->name, $this->store->list()); + $names = array_map(fn (\Utopia\NATS\ObjectStore\ObjectMeta $m): string => $m->name, $this->store->list()); sort($names); $this->assertSame(['a.txt', 'b.txt'], $names); diff --git a/packages/nats/tests/E2E/RequestManyTest.php b/packages/nats/tests/E2E/RequestManyTest.php index d5b5befaede..74fd1184238 100644 --- a/packages/nats/tests/E2E/RequestManyTest.php +++ b/packages/nats/tests/E2E/RequestManyTest.php @@ -41,7 +41,7 @@ public function testCollectsAllResponders(): void $replies = $conn->requestMany($subject, 'ping', ['timeout' => 2.0]); $this->assertCount(3, $replies); - $bodies = array_map(fn(\Utopia\NATS\Message $m): string => $m->data, $replies); + $bodies = array_map(fn (\Utopia\NATS\Message $m): string => $m->data, $replies); sort($bodies); $this->assertSame(['r1', 'r2', 'r3'], $bodies); diff --git a/packages/nats/tests/E2E/RequestReplyTest.php b/packages/nats/tests/E2E/RequestReplyTest.php index 14488fd86e2..0cf72e92056 100644 --- a/packages/nats/tests/E2E/RequestReplyTest.php +++ b/packages/nats/tests/E2E/RequestReplyTest.php @@ -44,7 +44,7 @@ public function testRequestTimeout(): void // Subscribe a silent responder so the server sees interest and does not // short-circuit with a "no responders" reply — the request must hang // until it times out. - $conn->subscribe('test.silent', fn($msg) => null); + $conn->subscribe('test.silent', fn ($msg) => null); $this->expectException(TimeoutException::class); $conn->request('test.silent', 'hello', 0.5); diff --git a/packages/nats/tests/E2E/ServiceExtrasTest.php b/packages/nats/tests/E2E/ServiceExtrasTest.php index 66480262dda..4213ab640ef 100644 --- a/packages/nats/tests/E2E/ServiceExtrasTest.php +++ b/packages/nats/tests/E2E/ServiceExtrasTest.php @@ -41,11 +41,11 @@ public function testGroupedEndpointIsReachableAtPrefixedSubject(): void { // Root group carries the unique service name so subjects don't collide. $group = $this->service->addGroup($this->name)->addGroup('math'); - $group->addEndpoint('add', fn(Message $msg): string => 'sum:' . $msg->data); + $group->addEndpoint('add', fn (Message $msg): string => 'sum:' . $msg->data); // Nested group prefixes cumulatively. $sub = $group->addGroup('trig'); - $sub->addEndpoint('sin', fn(Message $msg): string => 'sin:' . $msg->data); + $sub->addEndpoint('sin', fn (Message $msg): string => 'sin:' . $msg->data); $this->service->start(); @@ -84,7 +84,7 @@ public function testInfoIncludesEndpointsWithSubjectsAndMetadata(): void $group = $this->service->addGroup($this->name)->addGroup('v1'); $group->addEndpoint( 'status', - fn(Message $msg): string => 'ok', + fn (Message $msg): string => 'ok', null, null, ['visibility' => 'public'], @@ -112,13 +112,13 @@ public function testCustomQueueGroupIsHonored(): void // Group-level queue group is inherited by its endpoints. $group = $this->service->addGroup('jobs', $queue); - $group->addEndpoint('run', fn(Message $msg): string => 'done'); + $group->addEndpoint('run', fn (Message $msg): string => 'done'); // Per-endpoint override on the bare service. $this->service->addEndpoint( 'direct', "{$this->name}.direct", - fn(Message $msg): string => 'direct', + fn (Message $msg): string => 'direct', $queue, ); $this->service->start(); @@ -139,8 +139,8 @@ public function testQueueGroupLoadBalancesAcrossInstances(): void // Two endpoints on the same subject + queue group behave as two queue // members: NATS delivers each request to only one of them. - $this->service->addEndpoint('a', $subject, fn(Message $msg): string => 'a', $queue); - $this->service->addEndpoint('b', $subject, fn(Message $msg): string => 'b', $queue); + $this->service->addEndpoint('a', $subject, fn (Message $msg): string => 'a', $queue); + $this->service->addEndpoint('b', $subject, fn (Message $msg): string => 'b', $queue); $this->service->start(); $seen = ['a' => 0, 'b' => 0]; diff --git a/packages/nats/tests/E2E/ServiceTest.php b/packages/nats/tests/E2E/ServiceTest.php index 127d4177aea..b7b3544ead2 100644 --- a/packages/nats/tests/E2E/ServiceTest.php +++ b/packages/nats/tests/E2E/ServiceTest.php @@ -24,7 +24,7 @@ protected function setUp(): void $this->echoSubject = "{$this->name}.echo"; $this->service = new Service($this->conn, $this->name, '1.2.3', 'Test service'); - $this->service->addEndpoint('echo', $this->echoSubject, fn(Message $msg): string => 'echo:' . $msg->data); + $this->service->addEndpoint('echo', $this->echoSubject, fn (Message $msg): string => 'echo:' . $msg->data); $this->service->addEndpoint('boom', "{$this->name}.boom", function (Message $msg): string { throw new \RuntimeException('kaboom'); }); @@ -63,7 +63,7 @@ public function testInfoListsEndpoints(): void $this->assertSame('io.nats.micro.v1.info_response', $data['type']); $this->assertSame('Test service', $data['description']); - $subjects = array_map(fn(array $e) => $e['subject'], $data['endpoints']); + $subjects = array_map(fn (array $e) => $e['subject'], $data['endpoints']); $this->assertContains($this->echoSubject, $subjects); } diff --git a/packages/nats/tests/E2E/WebSocketTest.php b/packages/nats/tests/E2E/WebSocketTest.php index cb4999a6711..2a7fbefbbfd 100644 --- a/packages/nats/tests/E2E/WebSocketTest.php +++ b/packages/nats/tests/E2E/WebSocketTest.php @@ -34,7 +34,7 @@ public function testConnectAndRoundTripOverWebSocket(): void { $conn = Connection::connect(new ConnectionOptions( servers: $this->url, - transportFactory: fn(string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), + transportFactory: fn (string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), )); $this->assertTrue($conn->isConnected()); @@ -53,7 +53,7 @@ public function testRequestReplyOverWebSocket(): void { $conn = Connection::connect(new ConnectionOptions( servers: $this->url, - transportFactory: fn(string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), + transportFactory: fn (string $scheme): WebSocketTransport => new WebSocketTransport(secure: $scheme === 'wss'), )); $conn->subscribe('ws.service', function ($msg) use ($conn): void { diff --git a/packages/nats/tests/Unit/HeadersTest.php b/packages/nats/tests/HeadersTest.php similarity index 99% rename from packages/nats/tests/Unit/HeadersTest.php rename to packages/nats/tests/HeadersTest.php index 55eec374887..a800892f51c 100644 --- a/packages/nats/tests/Unit/HeadersTest.php +++ b/packages/nats/tests/HeadersTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Headers; diff --git a/packages/nats/tests/Unit/InboxTest.php b/packages/nats/tests/InboxTest.php similarity index 96% rename from packages/nats/tests/Unit/InboxTest.php rename to packages/nats/tests/InboxTest.php index cd266d5df94..145a084c546 100644 --- a/packages/nats/tests/Unit/InboxTest.php +++ b/packages/nats/tests/InboxTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Inbox; diff --git a/packages/nats/tests/Unit/JetStream/ConfigTest.php b/packages/nats/tests/JetStream/ConfigTest.php similarity index 98% rename from packages/nats/tests/Unit/JetStream/ConfigTest.php rename to packages/nats/tests/JetStream/ConfigTest.php index 3f4a76a474f..15dfbe4c3bd 100644 --- a/packages/nats/tests/Unit/JetStream/ConfigTest.php +++ b/packages/nats/tests/JetStream/ConfigTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\JetStream; +namespace Utopia\NATS\Tests\JetStream; use PHPUnit\Framework\TestCase; use Utopia\NATS\JetStream\ConsumerConfig; diff --git a/packages/nats/tests/Unit/ParserFrameIntegrityTest.php b/packages/nats/tests/ParserFrameIntegrityTest.php similarity index 97% rename from packages/nats/tests/Unit/ParserFrameIntegrityTest.php rename to packages/nats/tests/ParserFrameIntegrityTest.php index 445665638ae..f20a756e98b 100644 --- a/packages/nats/tests/Unit/ParserFrameIntegrityTest.php +++ b/packages/nats/tests/ParserFrameIntegrityTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Exception\ConnectionException; use Utopia\NATS\Exception\TimeoutException; use Utopia\NATS\Protocol\Parser; use Utopia\NATS\Protocol\ServerOp; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * A frame body that fails halfway through leaves the buffer mid-frame, so the diff --git a/packages/nats/tests/Unit/Protocol/ParserTest.php b/packages/nats/tests/Protocol/ParserTest.php similarity index 95% rename from packages/nats/tests/Unit/Protocol/ParserTest.php rename to packages/nats/tests/Protocol/ParserTest.php index 1ceafcbed1b..7c52373a81b 100644 --- a/packages/nats/tests/Unit/Protocol/ParserTest.php +++ b/packages/nats/tests/Protocol/ParserTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Protocol; +namespace Utopia\NATS\Tests\Protocol; use PHPUnit\Framework\TestCase; use Utopia\NATS\Exception\ProtocolException; @@ -17,9 +17,13 @@ private function createParser(string $data): Parser $transport = new class ($data) implements Transport { private int $pos = 0; - public function __construct(private readonly string $data) {} + public function __construct(private readonly string $data) + { + } - public function connect(string $host, int $port, float $timeout): void {} + public function connect(string $host, int $port, float $timeout): void + { + } public function write(string $data): int { @@ -47,12 +51,16 @@ public function readLine(?float $timeout = null): string return $line; } - public function upgradeTls(array $options): void {} + public function upgradeTls(array $options): void + { + } public function isConnected(): bool { return true; } - public function close(): void {} + public function close(): void + { + } }; return new Parser($transport); diff --git a/packages/nats/tests/Unit/Protocol/WriterTest.php b/packages/nats/tests/Protocol/WriterTest.php similarity index 98% rename from packages/nats/tests/Unit/Protocol/WriterTest.php rename to packages/nats/tests/Protocol/WriterTest.php index 8e9dc354bd9..61b3b3e9c67 100644 --- a/packages/nats/tests/Unit/Protocol/WriterTest.php +++ b/packages/nats/tests/Protocol/WriterTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Protocol; +namespace Utopia\NATS\Tests\Protocol; use PHPUnit\Framework\TestCase; use Utopia\NATS\Protocol\Writer; diff --git a/packages/nats/tests/Unit/ReconnectBackoffTest.php b/packages/nats/tests/ReconnectBackoffTest.php similarity index 98% rename from packages/nats/tests/Unit/ReconnectBackoffTest.php rename to packages/nats/tests/ReconnectBackoffTest.php index 29ff61e8a47..dc6c5f237f9 100644 --- a/packages/nats/tests/Unit/ReconnectBackoffTest.php +++ b/packages/nats/tests/ReconnectBackoffTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; diff --git a/packages/nats/tests/Unit/RequestsTest.php b/packages/nats/tests/RequestsTest.php similarity index 91% rename from packages/nats/tests/Unit/RequestsTest.php rename to packages/nats/tests/RequestsTest.php index d443ad5a712..eab46743072 100644 --- a/packages/nats/tests/Unit/RequestsTest.php +++ b/packages/nats/tests/RequestsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -15,7 +15,7 @@ use Utopia\NATS\Headers; use Utopia\NATS\Message; use Utopia\NATS\Request; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; final class RequestsTest extends TestCase { @@ -29,7 +29,7 @@ public static function reconnect(): iterable public function testFailedPongFailsEveryPendingRequestAndDisconnects(bool $reconnect): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(allowReconnect: $reconnect, transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(allowReconnect: $reconnect, transportFactory: fn (): FakeTransport => $fake)); $fake->onWrite = static function (string $wire, FakeTransport $fake): void { if (str_starts_with($wire, 'PUB ')) { $fake->pushInbound("PING\r\n"); @@ -60,7 +60,7 @@ public function testClosingErrorDisconnectsBeforeThrowingCallback(bool $reconnec $this->assertFalse($connection->isConnected()); throw $failure; }, - transportFactory: fn(): FakeTransport => $fake, + transportFactory: fn (): FakeTransport => $fake, )); $fake->onWrite = static function (string $wire, FakeTransport $fake): void { if (str_starts_with($wire, 'PUB ')) { @@ -99,7 +99,7 @@ public function testBufferedPongAndRepliesAreReadBeforeKeepalive(bool $reconnect allowReconnect: $reconnect, pingInterval: 0.0, maxPingsOut: 1, - transportFactory: fn(): FakeTransport => $fake, + transportFactory: fn (): FakeTransport => $fake, )); $fake->answerPings = false; $subjects = []; @@ -150,7 +150,7 @@ public function testKeepaliveFailureDoesNotReplayRequests(bool $batch, bool $fai maxReconnectAttempts: 1, pingInterval: 0.0, maxPingsOut: 1, - transportFactory: fn(): FakeTransport => $fake, + transportFactory: fn (): FakeTransport => $fake, )); $fake->answerPings = false; $before = substr_count($fake->written, "PING\r\n"); @@ -187,7 +187,7 @@ public function testKeepaliveFailureDoesNotReplayRequests(bool $batch, bool $fai public function testPipelinesRequestsAndRetainsOutOfOrderPartialReplies(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $groups = []; $fake->onWrite = static function (string $wire, FakeTransport $fake) use (&$groups): void { preg_match_all('/PUB work\.(\d+) ([^ ]+) 0\r\n\r\n/', $wire, $matches, PREG_SET_ORDER); @@ -219,9 +219,9 @@ public function testPipelinesRequestsAndRetainsOutOfOrderPartialReplies(): void public function testSubscriptionCallbackExceptionIsNotATransportFailure(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $failure = new ConnectionException('Application callback failed'); - $connection->subscribe('events', static fn() => throw $failure); + $connection->subscribe('events', static fn () => throw $failure); $fake->onWrite = static function (string $wire, FakeTransport $fake): void { if (str_starts_with($wire, 'PUB ')) { $fake->pushInbound("MSG events 1 1\r\nx\r\n"); @@ -244,11 +244,11 @@ public function testSubscriptionCallbackExceptionIsNotATransportFailure(): void public function testSingleAndBatchRequestsShareSubjectValidation(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $before = $fake->written; $refused = 0; foreach (['', 'bad subject', 'work.*', 'work..x'] as $subject) { - foreach ([fn(): \Utopia\NATS\Request => new Request($subject), fn(): \Utopia\NATS\Message => $connection->request($subject)] as $create) { + foreach ([fn (): \Utopia\NATS\Request => new Request($subject), fn (): \Utopia\NATS\Message => $connection->request($subject)] as $create) { try { $create(); self::fail('Invalid subject must be refused'); @@ -265,7 +265,7 @@ public function testSingleAndBatchRequestsShareSubjectValidation(): void public function testAmbiguousWriteIsNotReplayedAndDisconnects(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $writes = 0; $fake->onWrite = static function (string $wire) use (&$writes): void { if (str_starts_with($wire, 'PUB ')) { @@ -287,7 +287,7 @@ public function testAmbiguousWriteIsNotReplayedAndDisconnects(): void public function testCallbackFailureAbortsAndLeavesConnectionUsable(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(subPendingMsgsLimit: 1, onSlowConsumer: static fn() => self::fail('Late replies must not accumulate'), transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(subPendingMsgsLimit: 1, onSlowConsumer: static fn () => self::fail('Late replies must not accumulate'), transportFactory: fn (): FakeTransport => $fake)); $this->respond($fake); $seen = []; // Even transport-shaped callback errors must not become request failures. @@ -309,7 +309,7 @@ public function testCallbackFailureAbortsAndLeavesConnectionUsable(): void public function testHeadersAndNoRespondersRetainIndependentOutcomes(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $observed = ''; $fake->onWrite = static function (string $wire, FakeTransport $fake) use (&$observed): void { if (preg_match('/HPUB work ([^ ]+) (\d+) (\d+)\r\n/', $wire, $match)) { @@ -351,7 +351,7 @@ public static function invalid(): iterable public function testInvalidInputDoesNotWriteOrInvokeCallbacks(array $requests, float $timeout): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $before = $fake->written; $called = false; try { @@ -369,21 +369,21 @@ public function testInvalidInputDoesNotWriteOrInvokeCallbacks(array $requests, f public function testEmptyInputNeedsNoConnection(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $connection->close(); $before = $fake->written; - $connection->requestBatch([], static fn() => self::fail('Empty batch must not invoke callback')); + $connection->requestBatch([], static fn () => self::fail('Empty batch must not invoke callback')); $this->assertSame($before, $fake->written); } public function testOversizedLaterRequestDoesNotPublishEarlierRequests(): void { $fake = new FakeTransport(['max_payload' => 16]); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); try { $connection->requestBatch([ new Request(subject: 'work'), new Request(subject: 'work', data: str_repeat('x', 17)), - ], static fn() => self::fail('Validation must not invoke callback')); + ], static fn () => self::fail('Validation must not invoke callback')); self::fail('Payload validation must throw'); } catch (MaxPayloadException) { } @@ -396,11 +396,11 @@ public function testOversizedLaterRequestDoesNotPublishEarlierRequests(): void public function testCallbackCannotReadConnectionReentrantly(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $this->respond($fake); $refused = 0; $connection->requestBatch([new Request(subject: 'work')], function () use ($connection, &$refused): void { - foreach ([fn(): \Utopia\NATS\Message => $connection->request('work'), $connection->processMessage(...), fn(): array => $connection->requestMany('work'), fn() => $connection->wait(1), $connection->flush(...), $connection->tick(...), $connection->drain(...), fn() => $connection->requestBatch([], static fn() => null)] as $read) { + foreach ([fn (): \Utopia\NATS\Message => $connection->request('work'), $connection->processMessage(...), fn (): array => $connection->requestMany('work'), fn () => $connection->wait(1), $connection->flush(...), $connection->tick(...), $connection->drain(...), fn () => $connection->requestBatch([], static fn () => null)] as $read) { try { $read(); self::fail('Nested read must be refused'); @@ -417,7 +417,7 @@ public function testCallbackCannotReadConnectionReentrantly(): void public function testSharedDeadlineStartsAfterWriteAndIncludesCallbacks(): void { $fake = new FakeTransport(); - $connection = Connection::connect(new ConnectionOptions(transportFactory: fn(): FakeTransport => $fake)); + $connection = Connection::connect(new ConnectionOptions(transportFactory: fn (): FakeTransport => $fake)); $this->respond($fake); $respond = $fake->onWrite; $fake->onWrite = static function (string $wire, FakeTransport $fake) use ($respond): void { diff --git a/packages/nats/tests/Unit/SubscriptionSlowConsumerTest.php b/packages/nats/tests/SubscriptionSlowConsumerTest.php similarity index 99% rename from packages/nats/tests/Unit/SubscriptionSlowConsumerTest.php rename to packages/nats/tests/SubscriptionSlowConsumerTest.php index 04b9ee755c2..17c8911501e 100644 --- a/packages/nats/tests/Unit/SubscriptionSlowConsumerTest.php +++ b/packages/nats/tests/SubscriptionSlowConsumerTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Message; diff --git a/packages/nats/tests/Unit/Support/FakeTransport.php b/packages/nats/tests/Support/FakeTransport.php similarity index 98% rename from packages/nats/tests/Unit/Support/FakeTransport.php rename to packages/nats/tests/Support/FakeTransport.php index 83266dcd666..9d2f5e447f6 100644 --- a/packages/nats/tests/Unit/Support/FakeTransport.php +++ b/packages/nats/tests/Support/FakeTransport.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Support; +namespace Utopia\NATS\Tests\Support; use Utopia\NATS\Exception\ConnectionException; use Utopia\NATS\Exception\TimeoutException; @@ -34,7 +34,9 @@ final class FakeTransport implements Transport private bool $connected = false; /** @param array $info Fields merged into the served INFO. */ - public function __construct(private readonly array $info = []) {} + public function __construct(private readonly array $info = []) + { + } public function connect(string $host, int $port, float $timeout): void { diff --git a/packages/nats/tests/Unit/Support/PartialWriteStream.php b/packages/nats/tests/Support/PartialWriteStream.php similarity index 95% rename from packages/nats/tests/Unit/Support/PartialWriteStream.php rename to packages/nats/tests/Support/PartialWriteStream.php index 255d3904d92..dcc87a8bfd9 100644 --- a/packages/nats/tests/Unit/Support/PartialWriteStream.php +++ b/packages/nats/tests/Support/PartialWriteStream.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Support; +namespace Utopia\NATS\Tests\Support; /** * Stream wrapper that accepts at most $chunk bytes per write, forcing short diff --git a/packages/nats/tests/Unit/Transport/TcpTransportNoDelayTest.php b/packages/nats/tests/Transport/TcpTransportNoDelayTest.php similarity index 98% rename from packages/nats/tests/Unit/Transport/TcpTransportNoDelayTest.php rename to packages/nats/tests/Transport/TcpTransportNoDelayTest.php index 58b34c05484..52224e826b8 100644 --- a/packages/nats/tests/Unit/Transport/TcpTransportNoDelayTest.php +++ b/packages/nats/tests/Transport/TcpTransportNoDelayTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Transport; +namespace Utopia\NATS\Tests\Transport; use PHPUnit\Framework\TestCase; use Utopia\NATS\Transport\TcpTransport; diff --git a/packages/nats/tests/Unit/Transport/TcpTransportWriteTest.php b/packages/nats/tests/Transport/TcpTransportWriteTest.php similarity index 94% rename from packages/nats/tests/Unit/Transport/TcpTransportWriteTest.php rename to packages/nats/tests/Transport/TcpTransportWriteTest.php index 58c3d0f8bbe..2264a64d0b3 100644 --- a/packages/nats/tests/Unit/Transport/TcpTransportWriteTest.php +++ b/packages/nats/tests/Transport/TcpTransportWriteTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit\Transport; +namespace Utopia\NATS\Tests\Transport; use PHPUnit\Framework\TestCase; -use Utopia\NATS\Tests\Unit\Support\PartialWriteStream; +use Utopia\NATS\Tests\Support\PartialWriteStream; use Utopia\NATS\Transport\TcpTransport; /** diff --git a/packages/nats/tests/Unit/WriteFailureSemanticsTest.php b/packages/nats/tests/WriteFailureSemanticsTest.php similarity index 95% rename from packages/nats/tests/Unit/WriteFailureSemanticsTest.php rename to packages/nats/tests/WriteFailureSemanticsTest.php index c3b5fdd4893..5f8527baeb1 100644 --- a/packages/nats/tests/Unit/WriteFailureSemanticsTest.php +++ b/packages/nats/tests/WriteFailureSemanticsTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Utopia\NATS\Tests\Unit; +namespace Utopia\NATS\Tests; use PHPUnit\Framework\TestCase; use Utopia\NATS\Connection; @@ -12,7 +12,7 @@ use Utopia\NATS\Exception\TimeoutException; use Utopia\NATS\KeyValue\KeyValue; use Utopia\NATS\ObjectStore\ObjectStore; -use Utopia\NATS\Tests\Unit\Support\FakeTransport; +use Utopia\NATS\Tests\Support\FakeTransport; /** * KeyValue and ObjectStore guard their writes with optimistic concurrency, and @@ -33,7 +33,7 @@ private function unresponsiveConnection(FakeTransport $fake): Connection servers: 'nats://127.0.0.1:4222', allowReconnect: false, requestTimeout: 0.02, - transportFactory: fn(string $scheme): FakeTransport => $fake, + transportFactory: fn (string $scheme): FakeTransport => $fake, )); } From 7fbec2b0d760083851608c424c9abf400ff31496 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:15:32 +0100 Subject: [PATCH 423/470] docs(rfc): keep nats in packages/ and list its baseline for phase 8 --- rfc/monorepo.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rfc/monorepo.md b/rfc/monorepo.md index 5a86c9f202c..f7fdbb3092b 100644 --- a/rfc/monorepo.md +++ b/rfc/monorepo.md @@ -18,7 +18,7 @@ Every `utopia-php/*` library Appwrite depends on moves into this repository unde - Rewriting library APIs. Absorption moves code; API changes are separate work with their own releases. - Folding libraries into `src/Appwrite/`. Generic code stays generic and lives in `packages/`. `src/Utopia/` is gone: `Bus`, its only occupant, became `packages/bus` with a mirror like every other package. -- Moving the four monorepo packages Appwrite does not use. `fastly` is archived, `nats` and `replication` go to `appwrite/cloud`, `reputation` is undecided (Cloud or here). +- Moving the three monorepo packages Appwrite does not use. `fastly` and `replication` are archived, `reputation` is undecided (Cloud or here). `nats` was planned for `appwrite/cloud` but lives in `packages/nats`: its real consumer is `queue`'s NATS JetStream broker (`Broker\Nats`), and Cloud takes it through `server-ce`. - Adopting `utopia-php/config` 2.x. See [Version gaps](#version-gaps). ## Current state @@ -263,7 +263,7 @@ Exit: `composer.lock` contains no `utopia-php/*` package. ### Phase 7. Retire the old homes and move Cloud -- Archive `utopia-php/monorepo` with a README pointer here. Move `nats` and `replication` to `appwrite/cloud`; archive `fastly`; place `reputation` per the phase 0 decision. +- Archive `utopia-php/monorepo` with a README pointer here. Archive `fastly` and `replication` (`nats` already lives in `packages/`); place `reputation` per the phase 0 decision. - `appwrite/cloud` removes its `utopia-php/*` requirements and takes the classes through `server-ce`'s autoloader. Its stale pins (`validators ^0.5`, `span 3.0`, `usage 0.14`, `audit ^3`, `query 0.1`) disappear with them; that upgrade is Cloud's own PR series and is not blocked by anything here. ### Phase 8. Harvest @@ -287,6 +287,7 @@ Exit: `composer.lock` contains no `utopia-php/*` package. - `emails`: 25 findings, all in `src` (its standalone repository analysed it at level 4): unvalued `array` types on the canonical providers' domain lists and `Email`'s parts and domain caches, and concatenation of `mixed` parts in `Email`. - `http`: 394 findings (level 7 in the monorepo): 81 in `src`, casts and offset access on `mixed` request globals, Swoole server stats and the `__utopia__` coroutine context in the FPM and Swoole adapters, and the `mixed` param and injection definitions in `Http`; 313 in its tests, mostly calls on nullable `?Request`, `?Response`, `?Route` and `?Http` fixtures in `RequestTest`, `HttpTest` and `RouteTest`. - `mqtt`: 82 findings (its own repository analysed it at level max under PHPStan 1): `chr()` arguments not narrowed to `int<0, 255>` and casts from `mixed` in the packet codecs and `Property`, untyped Swoole client and request fields in `Client` and the Swoole adapter, and loosely typed data providers and e2e assertions in its tests. + - `nats`: 445 findings (level 5 in the monorepo): 292 in `src`, nearly all arguments, offset access and casts on the decoded `mixed` JSON in the `fromArray()` builders of the JetStream, KeyValue and ObjectStore configs and infos (`StreamConfig`, `ConsumerConfig`), `ServerInfo` and `Connection`; 153 in its tests, generator key types and decoded protocol frames in `ConnectionDeathDetectionTest`, `ParserTest` and `RequestsTest`, and decoded service replies in the `ServiceTest` and `ServiceExtrasTest` e2e tests. - `openapi`: 166 findings (level 5 in the monorepo), nearly all offset access on the decoded `mixed` document in its readers. - `platform`: 182 findings (level 5 in the monorepo): 146 in `src`, nearly all `mixed` values read from the untyped `array` param, option and label definitions and worker params in `Platform` and passed on to `Hook`, `Http`, `CLI` and the queue `Server`, plus unvalued `array` types in `Action` and `Module`; 36 in its tests, calls on the nullable `?Http` and `?Service` fixtures in `HttpServicesTest` and `WorkerServicesTest` and untyped `$response` parameters in the test actions. - `queue`: 296 findings (level 5 in the monorepo): 162 in `src`, unvalued `array` payloads and returns across `Connection`, `Message` and the brokers, and `mixed` Redis replies and decoded jobs in `Connection\Redis`, `Connection\RedisCluster`, `Broker\Redis`, `Broker\Pool` and `Server`; 134 in its tests, loosely typed connection fakes, the Swoole restart and proxy fixture servers, and decoded NATS and Redis payloads in its e2e tests. From 1f72af411c327a6335d0ccb10d9f8abefce78fe2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:28:45 +0100 Subject: [PATCH 424/470] (fix): default Google OAuth2 user and tokens to empty array on non-JSON response A failed or empty userinfo/token response makes json_decode return null, which cannot be assigned to the typed array properties and crashes the OAuth2 redirect with a TypeError. Falling back to [] lets the controller reach its existing USER_MISSING_ID failure redirect. --- src/Appwrite/Auth/OAuth2/Google.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Auth/OAuth2/Google.php b/src/Appwrite/Auth/OAuth2/Google.php index 3347956efa7..7c76d8f9eaa 100644 --- a/src/Appwrite/Auth/OAuth2/Google.php +++ b/src/Appwrite/Auth/OAuth2/Google.php @@ -77,7 +77,7 @@ protected function getTokens(string $code): array 'scope' => null, 'grant_type' => 'authorization_code' ]) - ), true); + ), true) ?? []; } return $this->tokens; @@ -98,7 +98,7 @@ public function refreshTokens(string $refreshToken): array 'client_secret' => $this->getClientSecret(), 'grant_type' => 'refresh_token' ]) - ), true); + ), true) ?? []; if (empty($this->tokens['refresh_token'])) { $this->tokens['refresh_token'] = $refreshToken; @@ -184,7 +184,7 @@ protected function getUser(string $accessToken): array { if (empty($this->user)) { $user = $this->request('GET', 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' . \urlencode($accessToken)); - $this->user = \json_decode($user, true); + $this->user = \json_decode($user, true) ?? []; } return $this->user; From 7d6fd787c28aeecdb337faabe9445253d7abfe83 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:08:12 +0530 Subject: [PATCH 425/470] fix(detector): detect TanStack Start SSR from the Nitro plugin Nitro always emits .output/server/index.mjs, even when every route is prerendered, so a Nitro config is ssr regardless of its prerender block. Without Nitro, a prerendered build is served from dist/client (static) unless prerender is disabled, including `prerender: { enabled: false }`. Replaces the routes/filter block parsing, which missed shorthand keys, spreads and nested objects. --- .../detector/src/Detection/Framework/TanStackStart.php | 6 +++--- packages/detector/tests/DetectorTest.php | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 42418876c8f..da61a2e3eb5 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -62,12 +62,12 @@ public function getAdapter(string $configContent): string { $stripped = \preg_replace('/(?assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart(), nitro({ config: { prerender: { routes: [\'/\'], crawlLinks: true } } })] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": { "filter": ({ path }) => path.startsWith(\'/docs\') } })] })')); - $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { crawlLinks: true } })] })')); + $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: false, crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); - $this->assertSame('ssr', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); + $this->assertSame('static', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); $this->assertNotEmpty($fw->getConfigFiles()); } From ecc5382ba57b22c2c158fbdc61a144a184c122a5 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:38:30 +0100 Subject: [PATCH 426/470] (fix): fail Google token exchange and refresh on invalid responses Defaulting a malformed token response to [] let a failed refresh overwrite the session's stored provider credentials with an empty access token, and let a failed code exchange continue to the userinfo call outside the controller's OAuth2 catch. Throwing the provider exception instead keeps the session untouched on refresh and routes the redirect to its provider-failure path. --- src/Appwrite/Auth/OAuth2/Google.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Auth/OAuth2/Google.php b/src/Appwrite/Auth/OAuth2/Google.php index 7c76d8f9eaa..be2e62c1d8d 100644 --- a/src/Appwrite/Auth/OAuth2/Google.php +++ b/src/Appwrite/Auth/OAuth2/Google.php @@ -67,7 +67,7 @@ public function getLoginURL(): string protected function getTokens(string $code): array { if (empty($this->tokens)) { - $this->tokens = \json_decode($this->request( + $this->tokens = $this->parseTokens($this->request( 'POST', 'https://oauth2.googleapis.com/token?' . \http_build_query([ 'code' => $code, @@ -77,7 +77,7 @@ protected function getTokens(string $code): array 'scope' => null, 'grant_type' => 'authorization_code' ]) - ), true) ?? []; + )); } return $this->tokens; @@ -90,7 +90,7 @@ protected function getTokens(string $code): array */ public function refreshTokens(string $refreshToken): array { - $this->tokens = \json_decode($this->request( + $this->tokens = $this->parseTokens($this->request( 'POST', 'https://oauth2.googleapis.com/token?' . \http_build_query([ 'refresh_token' => $refreshToken, @@ -98,7 +98,7 @@ public function refreshTokens(string $refreshToken): array 'client_secret' => $this->getClientSecret(), 'grant_type' => 'refresh_token' ]) - ), true) ?? []; + )); if (empty($this->tokens['refresh_token'])) { $this->tokens['refresh_token'] = $refreshToken; @@ -107,6 +107,24 @@ public function refreshTokens(string $refreshToken): array return $this->tokens; } + /** + * @param string $response + * + * @return array + * + * @throws Exception + */ + private function parseTokens(string $response): array + { + $tokens = \json_decode($response, true); + + if (!\is_array($tokens) || empty($tokens['access_token'])) { + throw new Exception($response, 424); + } + + return $tokens; + } + /** * @param string $accessToken * From f1a259051355a5ec75dbdc2d7641f17915043c4b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:12:10 +0530 Subject: [PATCH 427/470] docs(oauth2): use masked real Webflow credentials as examples --- .../Modules/Project/Http/Project/OAuth2/Webflow/Update.php | 4 ++-- src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php index f3c11d9fb45..6484ad0f5d6 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Webflow/Update.php @@ -40,7 +40,7 @@ public static function getClientIdName(): string public static function getClientIdExample(): string { - return '0d6a00000000000000000000000000000000000000000000000000000000f31c'; + return '8bb20000000000000000000000000000000000000000000000000000000040dd'; } public static function getClientSecretName(): string @@ -50,6 +50,6 @@ public static function getClientSecretName(): string public static function getClientSecretExample(): string { - return '7b1e00000000000000000000000000000000000000000000000000000000ac95'; + return '59bf00000000000000000000000000000000000000000000000000000000fe59'; } } diff --git a/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php b/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php index 0fe40034de3..3e21c3e55a5 100644 --- a/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php +++ b/src/Appwrite/Utopia/Response/Model/OAuth2Webflow.php @@ -17,12 +17,12 @@ public function getProviderLabel(): string public function getClientIdExample(): string { - return '0d6a00000000000000000000000000000000000000000000000000000000f31c'; + return '8bb20000000000000000000000000000000000000000000000000000000040dd'; } public function getClientSecretExample(): string { - return '7b1e00000000000000000000000000000000000000000000000000000000ac95'; + return '59bf00000000000000000000000000000000000000000000000000000000fe59'; } /** From 22a375fb1a9a4e00b20b58ff8fa4474782a21665 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:44:09 +0100 Subject: [PATCH 428/470] fix(nats): require PHP 8.3 for the typed class constants Connection declares typed class constants, which PHP 8.1 and 8.2 cannot parse; the manifest allowed >=8.1, so Composer could install the mirror on a runtime that fails to load it. --- packages/nats/README.md | 2 +- packages/nats/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nats/README.md b/packages/nats/README.md index b934ecff978..5636afa4d15 100644 --- a/packages/nats/README.md +++ b/packages/nats/README.md @@ -7,7 +7,7 @@ A modern PHP client for [NATS](https://nats.io) messaging system with JetStream ## Requirements -- PHP 8.1+ +- PHP 8.3+ - `ext-json` - `ext-sodium` (optional, for NKey/JWT authentication) diff --git a/packages/nats/composer.json b/packages/nats/composer.json index 85d8d38da47..df21463b659 100644 --- a/packages/nats/composer.json +++ b/packages/nats/composer.json @@ -11,7 +11,7 @@ "queue" ], "require": { - "php": ">=8.1", + "php": ">=8.3", "ext-json": "*" }, "suggest": { From 91eb13f12d299218a9948d392298930ff918153b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:20:37 +0530 Subject: [PATCH 429/470] fix(detector): match the Nitro call outside comments, without regex A commented-out nitro() or a leftover import no longer selects ssr: block comments are stripped and the check looks for the nitro() / nitroV2Plugin() call instead of the import path. The enabled: false check moves to string functions, leaving the existing prerender expression untouched. --- .../src/Detection/Framework/TanStackStart.php | 14 ++++++++++++-- packages/detector/tests/DetectorTest.php | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index da61a2e3eb5..61c02a360ac 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -62,12 +62,22 @@ public function getAdapter(string $configContent): string { $stripped = \preg_replace('/(?assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); + $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: false, crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); From b11d4f4031d9e18343bb7b54b08e61f1c9fac549 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:22:47 +0530 Subject: [PATCH 430/470] fix(detector): accept enabled: false anywhere in the prerender block --- packages/detector/src/Detection/Framework/TanStackStart.php | 3 ++- packages/detector/tests/DetectorTest.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 61c02a360ac..b878ff6cc52 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -73,7 +73,8 @@ public function getAdapter(string $configContent): string return 'ssr'; } - if (\str_contains($compact, 'prerender:{enabled:false')) { + $prerender = (string) \strstr((string) \strstr($compact, 'prerender:{'), '}', true); + if (\str_contains($prerender, 'enabled:false')) { return 'ssr'; } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 117b8a7b108..a88e43813cf 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -799,7 +799,7 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: false, crawlLinks: true } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { crawlLinks: true, enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); From cbeda184c12d0c9013cccb01bf4a7f8250bd70d2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 09:53:16 +0100 Subject: [PATCH 431/470] chore(nats): keep Rector's PHP 8.2/8.3 rules off the imported code Raising the PHP floor to 8.3 enabled ReadOnlyClassRector and AddTypeToConstRector (and an arrow-function return-type rule), so Rector's dry run in CI wanted to rewrite 44 files. The absorb keeps the source as released. --- packages/nats/rector.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/nats/rector.php b/packages/nats/rector.php index 827fa23c067..e912ff108d0 100644 --- a/packages/nats/rector.php +++ b/packages/nats/rector.php @@ -5,6 +5,9 @@ use Rector\Config\RectorConfig; use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; +use Rector\Php82\Rector\Class_\ReadOnlyClassRector; +use Rector\Php83\Rector\ClassConst\AddTypeToConstRector; +use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector; return RectorConfig::configure() ->withPaths([ @@ -20,4 +23,7 @@ ->withSkip([ ClassPropertyAssignToConstructorPromotionRector::class, ReadOnlyPropertyRector::class, + ReadOnlyClassRector::class, + AddTypeToConstRector::class, + AddArrowFunctionReturnTypeRector::class, ]); From fd7567290a81b6b4a4052ba4afb5c8982614761a Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:28:28 +0530 Subject: [PATCH 432/470] fix(oauth2): reject invalid Webflow credentials when enabling --- src/Appwrite/Auth/OAuth2/Webflow.php | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Appwrite/Auth/OAuth2/Webflow.php b/src/Appwrite/Auth/OAuth2/Webflow.php index 3fd5613f473..f999d2120c5 100644 --- a/src/Appwrite/Auth/OAuth2/Webflow.php +++ b/src/Appwrite/Auth/OAuth2/Webflow.php @@ -3,6 +3,10 @@ namespace Appwrite\Auth\OAuth2; use Appwrite\Auth\OAuth2; +use Utopia\Client\Adapter\Curl\Client as CurlAdapter; +use Utopia\Client\Client; +use Utopia\Psr7\Method; +use Utopia\Psr7\Request\Factory as RequestFactory; // Reference Material // https://developers.webflow.com/data/reference/oauth-app @@ -157,4 +161,31 @@ protected function getUser(string $accessToken): array return $this->user; } + + public function verifyCredentials(): void + { + $response = (new Client(new CurlAdapter())) + ->withTimeout(15) + ->withFollowRedirects(maxHops: 5) + ->sendRequest((new RequestFactory())->form( + Method::POST, + 'https://api.webflow.com/oauth/access_token', + [ + 'grant_type' => 'authorization_code', + 'client_id' => $this->appID, + 'client_secret' => $this->appSecret, + 'redirect_uri' => 'https://invalid.appwrite.callback/intentionally-invalid', + 'code' => 'intentionally-invalid-code', + ], + )); + + $json = \json_decode((string) $response->getBody(), true); + + if (isset($json['error']) && $json['error'] === 'invalid_client') { + throw new \Exception('Webflow application with the provided Client ID and/or Client Secret is invalid.'); + } + + // We still expect an error, like invalid_grant or invalid_request, + // but that indicates valid credentials + } } From d1a8396c73c57ca85a01445a3c5beae00c9f58c4 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:38:50 +0530 Subject: [PATCH 433/470] fix(detector): read the whole prerender block when it holds nested braces --- .../src/Detection/Framework/TanStackStart.php | 16 +++++++++++++++- packages/detector/tests/DetectorTest.php | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index b878ff6cc52..4fbcd07f55a 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -73,7 +73,21 @@ public function getAdapter(string $configContent): string return 'ssr'; } - $prerender = (string) \strstr((string) \strstr($compact, 'prerender:{'), '}', true); + // Cut at the brace that closes the block, so a `({ path }) =>` filter does not end it early. + $prerender = (string) \strstr($compact, 'prerender:{'); + for ($i = 10, $depth = 0; $i < \strlen($prerender); $i++) { + $depth += match ($prerender[$i]) { + '{' => 1, + '}' => -1, + default => 0, + }; + + if ($depth === 0) { + $prerender = \substr($prerender, 0, $i); + break; + } + } + if (\str_contains($prerender, 'enabled:false')) { return 'ssr'; } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index a88e43813cf..bcd43e491f5 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -799,7 +799,7 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { crawlLinks: true, enabled: false } })] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); From 73bc950d2868143cd443e5ee04a22da416dfe403 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:42:41 +0530 Subject: [PATCH 434/470] fix(detector): follow the Nitro import's local name, ignore strings The Nitro check reads the name the plugin is imported under, so `import { nitro as serverPlugin }` counts when serverPlugin() is called. String literals are emptied before looking for the call, so text such as 'nitro()' no longer selects ssr. --- .../src/Detection/Framework/TanStackStart.php | 29 +++++++++++++++++-- packages/detector/tests/DetectorTest.php | 2 ++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 4fbcd07f55a..f618d22a1b6 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -66,13 +66,36 @@ public function getAdapter(string $configContent): string $stripped = \substr($stripped, 0, $start) . \substr($stripped, $end + 2); } - $compact = \str_replace([' ', "\t", "\r", "\n", '"', "'"], '', $stripped); + // Empty every string literal, so text such as `'nitro()'` is never read as a call. + $code = ''; + $quote = ''; + for ($i = 0; $i < \strlen($stripped); $i++) { + $char = $stripped[$i]; + if ($quote === '') { + $code .= $char; + $quote = \in_array($char, ['\'', '"', '`'], true) ? $char : ''; + } elseif ($char === '\\') { + $i++; + } elseif ($char === $quote || ($char === "\n" && $quote !== '`')) { + $code .= $char; + $quote = ''; + } + } + $code = \str_replace([' ', "\t", "\r", "\n"], '', $code); // Nitro emits `.output/server/index.mjs` even when every route is prerendered. - if (\str_contains($compact, 'nitro(') || \str_contains($compact, 'nitroV2Plugin(')) { - return 'ssr'; + foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { + $specifiers = (string) \strstr((string) \strrchr((string) \strstr($stripped, $module, true), '{'), '}', true); + foreach (\explode(',', $specifiers) as $specifier) { + $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', "\t", "\r", "\n"], ' ', $specifier)))); + if (($names[0] ?? '') === $export && \str_contains($code, ($names[2] ?? $export) . '(')) { + return 'ssr'; + } + } } + $compact = \str_replace([' ', "\t", "\r", "\n", '"', "'"], '', $stripped); + // Cut at the brace that closes the block, so a `({ path }) =>` filter does not end it early. $prerender = (string) \strstr($compact, 'prerender:{'); for ($i = 10, $depth = 0; $i < \strlen($prerender); $i++) { diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index bcd43e491f5..18dde403132 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -799,6 +799,8 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); + $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); From 07a9fb81a99411d2b962c5c4e5e08cbc2048637b Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:45:11 +0530 Subject: [PATCH 435/470] fix(detector): count namespace calls such as nitroPlugin.nitro() --- .../src/Detection/Framework/TanStackStart.php | 12 +++++++++++- packages/detector/tests/DetectorTest.php | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index f618d22a1b6..d4c5aa6c148 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -85,7 +85,17 @@ public function getAdapter(string $configContent): string // Nitro emits `.output/server/index.mjs` even when every route is prerendered. foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { - $specifiers = (string) \strstr((string) \strrchr((string) \strstr($stripped, $module, true), '{'), '}', true); + $import = \strstr($stripped, $module, true); + if ($import === false) { + continue; + } + + // A namespace import calls the plugin as a member: `nitroPlugin.nitro()`. + if (\str_contains($code, '.' . $export . '(')) { + return 'ssr'; + } + + $specifiers = (string) \strstr((string) \strrchr($import, '{'), '}', true); foreach (\explode(',', $specifiers) as $specifier) { $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', "\t", "\r", "\n"], ' ', $specifier)))); if (($names[0] ?? '') === $export && \str_contains($code, ($names[2] ?? $export) . '(')) { diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 18dde403132..2370255cf4f 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -800,6 +800,7 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); + $this->assertSame('ssr', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitroPlugin.nitro()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); From 26cdf3678ecdd9091187930b7f34e4a722ac0c2e Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:52:33 +0530 Subject: [PATCH 436/470] fix(account): redirect native OAuth2 logins straight to the app callback When a native OAuth2 session uses the default success or failure URL, the redirect endpoint now sends the browser directly to appwrite-callback-{projectId}:// with the same query the console relay page used to forward (project, domain, key, secret, or error). Native login no longer depends on a console page loading and running JavaScript before it hands control back to the app. Custom success and failure URLs, and the token flow, are unchanged. --- app/controllers/api/account.php | 10 ++++++---- .../Services/Account/AccountCustomClientTest.php | 13 ++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ec54f2ad854..38e0291bf8b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1627,15 +1627,17 @@ if (!empty($state['failure']) && !$redirectValidator->isValid($state['failure'])) { throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL); } - // The default relays live on the console host; the same path on any other allowed host is a customer page + // The default relays live on the console host; the same path on any other allowed host is a customer page. + // Native apps skip the relay: its JavaScript redirect into the app is late or dropped on slow in-app browsers. $consoleHostname = \parse_url($platform['consoleUrl'] ?? '', PHP_URL_HOST); + $nativeCallback = ['scheme' => 'appwrite-callback-' . $project->getId()]; $failure = []; if (!empty($state['failure'])) { $failure = URLParser::parse($state['failure']); } - $failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure, $consoleHostname) { + $failureRedirect = (function (string $type, ?string $message = null, ?int $code = null, ?\Throwable $previous = null, array $params = []) use ($failure, $response, $project, $oauthDefaultFailure, $consoleHostname, $nativeCallback) { $exception = new Exception($type, $message, $code, $previous, params: $params); if (!empty($failure)) { $query = URLParser::parseQuery($failure['query']); @@ -1644,10 +1646,9 @@ 'type' => $exception->getType(), 'code' => !\is_null($code) ? $code : $exception->getCode(), ]); - // Mirror success path: default OAuth failure relay needs project to deep-link - // back into the native app via appwrite-callback-{project}:// if ($failure['host'] === $consoleHostname && $failure['path'] === $oauthDefaultFailure) { $query['project'] = $project->getId(); + $failure = $nativeCallback; } $failure['query'] = URLParser::unparseQuery($query); $response->redirect(URLParser::unparse($failure), 301); @@ -2234,6 +2235,7 @@ $query['domain'] = $cookieDomain; $query['key'] = $store->getKey(); $query['secret'] = $encoded; + $state['success'] = $nativeCallback; } $response diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index d1d0a00a425..71568f4ec4a 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -3443,9 +3443,8 @@ public function testOAuthUnverifiedEmailCannotLinkToExistingAccount(): void } /** - * Default OAuth failure relay pages need `project` so native apps can deep-link via - * appwrite-callback-{project}://. Without it the UI shows "Missing redirect URL" - * instead of the real OAuth error. + * The default OAuth failure URL redirects straight to appwrite-callback-{project}://, + * carrying `project` and the real OAuth error. */ public function testOAuthDefaultFailureRedirectIncludesProject(): void { @@ -3503,11 +3502,11 @@ public function testOAuthDefaultFailureRedirectIncludesProject(): void $this->assertEquals(301, $response['headers']['status-code']); $location = $response['headers']['location']; - $path = \parse_url($location, PHP_URL_PATH); - $query = []; - \parse_str((string) \parse_url($location, PHP_URL_QUERY), $query); + $this->assertStringStartsWith('appwrite-callback-' . $projectId . '://?', $location); - $this->assertEquals('/auth/oauth2/failure', $path); + // parse_url() rejects a scheme with no host, so read the query directly + $query = []; + \parse_str(\explode('?', $location, 2)[1], $query); $this->assertEquals($projectId, $query['project'] ?? null); $this->assertNotEmpty($query['error'] ?? null); From a0390f526b181a93270c94e2868ee284bcfd4027 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:54:30 +0530 Subject: [PATCH 437/470] fix(detector): tie Nitro calls to the imported name A namespace import only counts through its own name (nitroPlugin.nitro()), so settings.nitro() no longer selects ssr. The call must start at an identifier boundary, so a short alias such as n does not match inside paraglideVitePlugin(). --- .../src/Detection/Framework/TanStackStart.php | 23 +++++++++++-------- packages/detector/tests/DetectorTest.php | 2 ++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index d4c5aa6c148..186bfb6a7f4 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -81,7 +81,6 @@ public function getAdapter(string $configContent): string $quote = ''; } } - $code = \str_replace([' ', "\t", "\r", "\n"], '', $code); // Nitro emits `.output/server/index.mjs` even when every route is prerendered. foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { @@ -90,17 +89,23 @@ public function getAdapter(string $configContent): string continue; } - // A namespace import calls the plugin as a member: `nitroPlugin.nitro()`. - if (\str_contains($code, '.' . $export . '(')) { - return 'ssr'; - } + // What the config calls: `nitro`, `serverPlugin` for `{ nitro as serverPlugin }`, `nitroPlugin.nitro` for `* as nitroPlugin`. + $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', '}', ',', "\t", "\r", "\n"], ' ', \substr($import, (int) \strrpos($import, 'import ')))))); + $index = \array_search($export, $names, true); + $call = match (true) { + ($names[1] ?? '') === '*' && isset($names[3]) => $names[3] . '.' . $export, + $index !== false && ($names[$index + 1] ?? '') === 'as' => $names[$index + 2] ?? '', + $index !== false => $export, + default => '', + }; - $specifiers = (string) \strstr((string) \strrchr($import, '{'), '}', true); - foreach (\explode(',', $specifiers) as $specifier) { - $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', "\t", "\r", "\n"], ' ', $specifier)))); - if (($names[0] ?? '') === $export && \str_contains($code, ($names[2] ?? $export) . '(')) { + $offset = 0; + while ($call !== '' && ($at = \strpos($code, $call . '(', $offset)) !== false) { + $before = $at > 0 ? $code[$at - 1] : ' '; + if (!\ctype_alnum($before) && !\in_array($before, ['_', '$', '.'], true)) { return 'ssr'; } + $offset = $at + 1; } } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 2370255cf4f..0e9ed73ebf5 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -801,6 +801,8 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); $this->assertSame('ssr', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitroPlugin.nitro()] })')); + $this->assertSame('static', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), settings.nitro()] })')); + $this->assertSame('static', $fw->getAdapter('import { nitro as n } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), paraglideVitePlugin()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); From 3710156d8a79046e1f10ab2d11da50670655241d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 14:59:37 +0530 Subject: [PATCH 438/470] test(account): cover the default OAuth2 success redirect into the app --- .../Account/AccountCustomClientTest.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 71568f4ec4a..5618183ef2f 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -3442,6 +3442,58 @@ public function testOAuthUnverifiedEmailCannotLinkToExistingAccount(): void $this->assertEquals(204, $response['headers']['status-code']); } + /** + * The default OAuth success URL redirects straight to appwrite-callback-{project}://, + * carrying the session the native SDKs store. + */ + public function testOAuthDefaultSuccessRedirectsToApp(): void + { + $provider = 'mock'; + $projectId = $this->getProject()['$id']; + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/oauth2', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ]), [ + 'provider' => $provider, + 'appId' => '1', + 'secret' => '123456', + 'enabled' => true, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + // Omit success so Appwrite uses the default relay URL (/auth/oauth2/success) + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], followRedirects: false); + + $this->assertEquals(301, $response['headers']['status-code']); + + // Provider consent, Appwrite callback, Appwrite redirect + $oauthClient = new Client(); + $oauthClient->setEndpoint(''); + + for ($hop = 0; $hop < 3; $hop++) { + $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + } + + $location = $response['headers']['location']; + $this->assertStringStartsWith('appwrite-callback-' . $projectId . '://?', $location); + + // parse_url() rejects a scheme with no host, so read the query directly + $query = []; + \parse_str(\explode('?', $location, 2)[1], $query); + $this->assertEquals($projectId, $query['project'] ?? null); + $this->assertEquals('a_session_' . $projectId, $query['key'] ?? null); + $this->assertNotEmpty($query['secret'] ?? null); + } + /** * The default OAuth failure URL redirects straight to appwrite-callback-{project}://, * carrying `project` and the real OAuth error. From 030047a849cc107325e221f2fe00242cfd8c054c Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:01:45 +0530 Subject: [PATCH 439/470] fix(detector): read spaced Nitro calls, every import and quoted braces Whitespace that does not separate two names is dropped before looking for the call, so `nitro ()` and `serverPlugin\n()` count. Every import of a Nitro module is read, not only the first. Braces inside strings no longer end the prerender block. --- .../src/Detection/Framework/TanStackStart.php | 78 ++++++++++++------- packages/detector/tests/DetectorTest.php | 3 + 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 186bfb6a7f4..69f3c6a816f 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -66,51 +66,73 @@ public function getAdapter(string $configContent): string $stripped = \substr($stripped, 0, $start) . \substr($stripped, $end + 2); } - // Empty every string literal, so text such as `'nitro()'` is never read as a call. + // `$code` empties string literals, so `'nitro()'` is never read as a call, and keeps whitespace only between two names. + // `$compact` keeps the text of strings but drops quotes, whitespace and quoted braces, so `"enabled": false` still reads as a key. $code = ''; + $compact = ''; $quote = ''; + $gap = false; for ($i = 0; $i < \strlen($stripped); $i++) { $char = $stripped[$i]; - if ($quote === '') { - $code .= $char; - $quote = \in_array($char, ['\'', '"', '`'], true) ? $char : ''; - } elseif ($char === '\\') { - $i++; - } elseif ($char === $quote || ($char === "\n" && $quote !== '`')) { - $code .= $char; - $quote = ''; + if ($quote !== '') { + if ($char === '\\') { + $i++; + } elseif ($char === $quote || ($char === "\n" && $quote !== '`')) { + $code .= $char; + $quote = ''; + } elseif (!\in_array($char, ['{', '}'], true) && !\ctype_space($char)) { + $compact .= $char; + } + + continue; } - } - // Nitro emits `.output/server/index.mjs` even when every route is prerendered. - foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { - $import = \strstr($stripped, $module, true); - if ($import === false) { + if (\ctype_space($char)) { + $gap = true; + continue; } - // What the config calls: `nitro`, `serverPlugin` for `{ nitro as serverPlugin }`, `nitroPlugin.nitro` for `* as nitroPlugin`. - $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', '}', ',', "\t", "\r", "\n"], ' ', \substr($import, (int) \strrpos($import, 'import ')))))); - $index = \array_search($export, $names, true); - $call = match (true) { - ($names[1] ?? '') === '*' && isset($names[3]) => $names[3] . '.' . $export, - $index !== false && ($names[$index + 1] ?? '') === 'as' => $names[$index + 2] ?? '', - $index !== false => $export, - default => '', - }; + $last = \substr($code, -1); + if ($gap && (\ctype_alnum($last) || \in_array($last, ['_', '$'], true)) && (\ctype_alnum($char) || \in_array($char, ['_', '$'], true))) { + $code .= ' '; + } - $offset = 0; - while ($call !== '' && ($at = \strpos($code, $call . '(', $offset)) !== false) { + $gap = false; + $code .= $char; + if (\in_array($char, ['\'', '"', '`'], true)) { + $quote = $char; + } else { + $compact .= $char; + } + } + + // What each Nitro import is called as: `nitro`, `serverPlugin` for `{ nitro as serverPlugin }`, `nitroPlugin.nitro` for `* as nitroPlugin`. + $calls = []; + foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { + for ($at = \strpos($stripped, $module); $at !== false; $at = \strpos($stripped, $module, $at + 1)) { + $import = \substr($stripped, 0, $at); + $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', '}', ',', "\t", "\r", "\n"], ' ', \substr($import, (int) \strrpos($import, 'import ')))))); + $index = \array_search($export, $names, true); + $calls[] = match (true) { + ($names[1] ?? '') === '*' && isset($names[3]) => $names[3] . '.' . $export, + $index !== false && ($names[$index + 1] ?? '') === 'as' => $names[$index + 2] ?? '', + $index !== false => $export, + default => '', + }; + } + } + + // Nitro emits `.output/server/index.mjs` even when every route is prerendered. + foreach (\array_filter($calls) as $call) { + for ($at = \strpos($code, $call . '('); $at !== false; $at = \strpos($code, $call . '(', $at + 1)) { $before = $at > 0 ? $code[$at - 1] : ' '; if (!\ctype_alnum($before) && !\in_array($before, ['_', '$', '.'], true)) { return 'ssr'; } - $offset = $at + 1; } } - $compact = \str_replace([' ', "\t", "\r", "\n", '"', "'"], '', $stripped); - // Cut at the brace that closes the block, so a `({ path }) =>` filter does not end it early. $prerender = (string) \strstr($compact, 'prerender:{'); for ($i = 10, $depth = 0; $i < \strlen($prerender); $i++) { diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 0e9ed73ebf5..50cd58f5945 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -803,6 +803,9 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitroPlugin.nitro()] })')); $this->assertSame('static', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), settings.nitro()] })')); $this->assertSame('static', $fw->getAdapter('import { nitro as n } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), paraglideVitePlugin()] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitro ()] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitro as unused } from \'nitro/vite\'' . "\n" . 'import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); + $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'}\'], enabled: false } })] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); From db77881343d030d6ec86274f815af8839b5dd01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Sep 2026 11:36:44 +0200 Subject: [PATCH 440/470] test: cover session resolution under JWT auth Rename the JWT session tests to describe expected behavior, share JWT and session setup through helpers, and add coverage for updating the current session, marking the current session in the sessions list, and invalidating other sessions on password change under JWT. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Account/AccountCustomClientTest.php | 236 ++++++++---------- 1 file changed, 107 insertions(+), 129 deletions(-) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index e3a563bcab2..531f319109b 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -5452,7 +5452,53 @@ public function testMFAAuthenticatorCompletesEmailPasswordSession(): void $this->assertEquals($data['id'], $account['body']['$id']); } - public function testMFARecencyCheckFailsUnderJWT(): void + protected function createJWTHeaders(string $session): array + { + $projectId = $this->getProject()['$id']; + + $response = $this->client->call(Client::METHOD_POST, '/account/jwts', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $session, + ]); + $this->assertEquals(201, $response['headers']['status-code']); + + return [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $response['body']['jwt'], + ]; + } + + protected function createSessionCookie(string $email, string $password): array + { + $projectId = $this->getProject()['$id']; + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], [ + 'email' => $email, + 'password' => $password, + ]); + $this->assertEquals(201, $response['headers']['status-code']); + + return [ + 'id' => $response['body']['$id'], + 'secret' => $response['cookies']['a_session_' . $projectId], + 'headers' => [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $response['cookies']['a_session_' . $projectId], + ], + ]; + } + + public function testMFARecencyCheckUnderJWT(): void { $data = $this->createFreshAccountWithSession(); $projectId = $this->getProject()['$id']; @@ -5467,32 +5513,16 @@ public function testMFARecencyCheckFailsUnderJWT(): void $recoveryCodes = $this->client->call(Client::METHOD_POST, '/account/mfa/recovery-codes', $headers); $this->assertEquals(201, $recoveryCodes['headers']['status-code']); - // Cookie auth: correctly blocked, no recent MFA challenge on this session. $cookieAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $headers); $this->assertEquals(401, $cookieAttempt['headers']['status-code']); $this->assertEquals('user_challenge_required', $cookieAttempt['body']['type']); - // Same session, converted to a JWT (still no challenge completed). - $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $headers); - $this->assertEquals(201, $jwtResponse['headers']['status-code']); - $jwtHeaders = [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], - ]; - - // EXPECTED: identical 401 user_challenge_required — nothing about - // the underlying session state changed, only the auth transport. - // ACTUAL (bug): the 'session' resource resolves to null under JWT, and - // the mfaProtected middleware requires a non-null Document, so the - // request errors out instead of evaluating recency. - $jwtAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $jwtHeaders); + $jwtAttempt = $this->client->call(Client::METHOD_PATCH, '/account/mfa/recovery-codes', $this->createJWTHeaders($data['session'])); $this->assertEquals(401, $jwtAttempt['headers']['status-code']); $this->assertEquals('user_challenge_required', $jwtAttempt['body']['type']); } - public function testMFAFactorCountBypassedUnderJWT(): void + public function testMFAFactorCountUnderJWT(): void { $data = $this->createFreshAccountWithSession(); $projectId = $this->getProject()['$id']; @@ -5504,32 +5534,11 @@ public function testMFAFactorCountBypassedUnderJWT(): void 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], ]; - // Second password-only session, created while MFA is still off. - $sessionB = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], [ - 'email' => $data['email'], - 'password' => $data['password'], - ]); - $this->assertEquals(201, $sessionB['headers']['status-code']); - $this->assertEquals(['password'], $sessionB['body']['factors']); - - $sessionBHeaders = [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $sessionB['cookies']['a_session_' . $projectId], - ]; + $sessionB = $this->createSessionCookie($data['email'], $data['password']); - // The JWT must be issued now: /account/jwts runs the same factor-count - // check, so it would be refused once session B is under-verified. - $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', $sessionBHeaders); - $this->assertEquals(201, $jwtResponse['headers']['status-code']); + // Issued before MFA is enabled, since session B could not create a JWT afterwards + $jwtHeaders = $this->createJWTHeaders($sessionB['secret']); - // Enable MFA from session A. Only the calling session gains the totp - // factor; session B stays at ['password'], below the new 2-factor minimum. $authenticator = $this->client->call(Client::METHOD_POST, '/account/mfa/authenticators/totp', $headers); $this->assertEquals(200, $authenticator['headers']['status-code']); @@ -5542,124 +5551,93 @@ public function testMFAFactorCountBypassedUnderJWT(): void $mfa = $this->client->call(Client::METHOD_PATCH, '/account/mfa', $headers, ['mfa' => true]); $this->assertEquals(200, $mfa['headers']['status-code']); - // Cookie auth on session B: correctly blocked, only 1 of 2 required factors present. - $cookieAttempt = $this->client->call(Client::METHOD_GET, '/account', $sessionBHeaders); + $cookieAttempt = $this->client->call(Client::METHOD_GET, '/account', $sessionB['headers']); $this->assertEquals(401, $cookieAttempt['headers']['status-code']); $this->assertEquals('user_more_factors_required', $cookieAttempt['body']['type']); - $jwtHeaders = [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], - ]; - - // EXPECTED: identical 401 user_more_factors_required. - // ACTUAL (bug): shared/api.php guards this check with `if ($session && ...)` - // — under JWT, $session is null, so the check is silently skipped - // and the request succeeds despite the missing second factor. $jwtAttempt = $this->client->call(Client::METHOD_GET, '/account', $jwtHeaders); $this->assertEquals(401, $jwtAttempt['headers']['status-code']); $this->assertEquals('user_more_factors_required', $jwtAttempt['body']['type']); } - public function testDeleteSessionCurrentWrongUnderJWT(): void + public function testGetSessionCurrentUnderJWT(): void { $data = $this->createFreshAccountWithSession(); - $projectId = $this->getProject()['$id']; - $sessionAId = $data['sessionId']; - $sessionA = $data['session']; + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', $this->createJWTHeaders($data['session'])); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($data['sessionId'], $response['body']['$id']); + } - $sessionBResponse = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], [ - 'email' => $data['email'], - 'password' => $data['password'], - ]); - $this->assertEquals(201, $sessionBResponse['headers']['status-code']); - $sessionB = $sessionBResponse['cookies']['a_session_' . $projectId]; + public function testUpdateSessionCurrentUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); - // Get a JWT backed by session A specifically. - $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $sessionA, - ]); - $this->assertEquals(201, $jwtResponse['headers']['status-code']); - $jwtHeaders = [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], - ]; + $response = $this->client->call(Client::METHOD_PATCH, '/account/sessions/current', $this->createJWTHeaders($data['session'])); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($data['sessionId'], $response['body']['$id']); + } - // EXPECTED: 'current' resolves to session A (the JWT-backing - // session) and deletes it — 204, matching what a cookie-authenticated - // caller gets from the same "current" keyword. - // ACTUAL (bug): sessionVerify() can't resolve under JWT, so the - // lookup loop never matches any session, and this throws - // 404 user_session_not_found instead of deleting session A. - $delete = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', $jwtHeaders); + public function testDeleteSessionCurrentUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $sessionB = $this->createSessionCookie($data['email'], $data['password']); + + $delete = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', $this->createJWTHeaders($data['session'])); $this->assertEquals(204, $delete['headers']['status-code']); - // Session A should now be gone... $checkA = $this->client->call(Client::METHOD_GET, '/account', [ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $sessionA, + 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], ]); $this->assertEquals(401, $checkA['headers']['status-code']); - // ...and session B should be untouched. - $checkB = $this->client->call(Client::METHOD_GET, '/account', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $sessionB, - ]); + $checkB = $this->client->call(Client::METHOD_GET, '/account', $sessionB['headers']); $this->assertEquals(200, $checkB['headers']['status-code']); + } - // Clean up. - $this->client->call(Client::METHOD_DELETE, '/account/sessions', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $sessionB, - ]); + public function testListSessionsCurrentUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + + $sessionB = $this->createSessionCookie($data['email'], $data['password']); + + $response = $this->client->call(Client::METHOD_GET, '/account/sessions', $this->createJWTHeaders($data['session'])); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(2, $response['body']['total']); + + $current = []; + foreach ($response['body']['sessions'] as $session) { + $current[$session['$id']] = $session['current']; + } + + $this->assertTrue($current[$data['sessionId']]); + $this->assertFalse($current[$sessionB['id']]); } - public function testGetSessionCurrentWrongUnderJWT(): void + public function testUpdatePasswordInvalidatesOtherSessionsUnderJWT(): void { $data = $this->createFreshAccountWithSession(); - $projectId = $this->getProject()['$id']; + $this->updateProjectinvalidateSessionsProperty(true); - $jwtResponse = $this->client->call(Client::METHOD_POST, '/account/jwt', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], - ]); - $this->assertEquals(201, $jwtResponse['headers']['status-code']); - $jwtHeaders = [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-jwt' => $jwtResponse['body']['jwt'], - ]; + $sessionB = $this->createSessionCookie($data['email'], $data['password']); + $jwtHeaders = $this->createJWTHeaders($data['session']); - // EXPECTED: 'current' resolves to the JWT-backing session and - // returns it — 200, matching cookie-auth behavior for the same - // "current" keyword. - // ACTUAL (bug): sessionVerify() can't resolve under JWT, so this - // throws 404 user_session_not_found instead of returning the session. - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', $jwtHeaders); + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', $jwtHeaders, [ + 'password' => 'new-password', + 'oldPassword' => $data['password'], + ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($data['sessionId'], $response['body']['$id']); + + $checkB = $this->client->call(Client::METHOD_GET, '/account', $sessionB['headers']); + $this->assertEquals(401, $checkB['headers']['status-code']); + + $checkJWT = $this->client->call(Client::METHOD_GET, '/account', $jwtHeaders); + $this->assertEquals(200, $checkJWT['headers']['status-code']); } public function testRefreshEmailPasswordSession(): void From 27a10434028c5af8935450ffdfd37386ea47f5a2 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:07:31 +0530 Subject: [PATCH 441/470] test(account): assert the default OAuth2 handoff opens a working session --- .../Account/AccountCustomClientTest.php | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 5618183ef2f..4a3f8dee66e 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2535,7 +2535,10 @@ public function testOAuth2TokenSessionProviderAccessToken(): void * app's success URL. The headers (a session cookie) ride along on every * hop, like a browser would send them. Returns the final redirect response. */ - private function followMockOAuth2Flow(string $path, array $headers = []): array + private function followMockOAuth2Flow(string $path, array $headers = [], array $params = [ + 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', + 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', + ]): array { $projectId = $this->getProject()['$id']; @@ -2543,10 +2546,7 @@ private function followMockOAuth2Flow(string $path, array $headers = []): array 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - ], $headers), [ - 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', - 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', - ], followRedirects: false); + ], $headers), $params, followRedirects: false); $this->assertEquals(301, $response['headers']['status-code']); @@ -3465,23 +3465,8 @@ public function testOAuthDefaultSuccessRedirectsToApp(): void $this->assertEquals(200, $response['headers']['status-code']); - // Omit success so Appwrite uses the default relay URL (/auth/oauth2/success) - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], followRedirects: false); - - $this->assertEquals(301, $response['headers']['status-code']); - - // Provider consent, Appwrite callback, Appwrite redirect - $oauthClient = new Client(); - $oauthClient->setEndpoint(''); - - for ($hop = 0; $hop < 3; $hop++) { - $response = $oauthClient->call(Client::METHOD_GET, $response['headers']['location'], followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - } + // Omit success and failure so Appwrite uses the default relay URLs + $response = $this->followMockOAuth2Flow('/account/sessions/oauth2/' . $provider, params: []); $location = $response['headers']['location']; $this->assertStringStartsWith('appwrite-callback-' . $projectId . '://?', $location); @@ -3489,9 +3474,16 @@ public function testOAuthDefaultSuccessRedirectsToApp(): void // parse_url() rejects a scheme with no host, so read the query directly $query = []; \parse_str(\explode('?', $location, 2)[1], $query); - $this->assertEquals($projectId, $query['project'] ?? null); - $this->assertEquals('a_session_' . $projectId, $query['key'] ?? null); - $this->assertNotEmpty($query['secret'] ?? null); + + // Native SDKs store the handoff as their session cookie + $response = $this->client->call(Client::METHOD_GET, '/account', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => ($query['key'] ?? '') . '=' . ($query['secret'] ?? ''), + ]); + + $this->assertEquals(200, $response['headers']['status-code']); } /** From 55f3a49fb8c7e9ab5acc16ee86778e543f167465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Sep 2026 11:39:47 +0200 Subject: [PATCH 442/470] test: cover push target rotation under JWT auth Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Account/AccountCustomClientTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 531f319109b..4097b1c1732 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -5640,6 +5640,25 @@ public function testUpdatePasswordInvalidatesOtherSessionsUnderJWT(): void $this->assertEquals(200, $checkJWT['headers']['status-code']); } + public function testCreatePushTargetReplacesRotatedTokenUnderJWT(): void + { + $data = $this->createFreshAccountWithSession(); + $jwtHeaders = $this->createJWTHeaders($data['session']); + + $first = $this->client->call(Client::METHOD_POST, '/account/targets/push', $jwtHeaders, [ + 'targetId' => ID::unique(), + 'identifier' => 'jwt-identifier-before-rotation-' . $data['id'], + ]); + $this->assertEquals(201, $first['headers']['status-code']); + + $second = $this->client->call(Client::METHOD_POST, '/account/targets/push', $jwtHeaders, [ + 'targetId' => ID::unique(), + 'identifier' => 'jwt-identifier-after-rotation-' . $data['id'], + ]); + $this->assertEquals(201, $second['headers']['status-code']); + $this->assertEquals($first['body']['$id'], $second['body']['$id']); + } + public function testRefreshEmailPasswordSession(): void { $email = uniqid() . 'user@localhost.test'; From 2c24de9309da9c2f67dfd8dc7ed86913d2ba9601 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:11:07 +0530 Subject: [PATCH 443/470] fix(detector): read Nitro only from import sources and real calls A module path counts only as `from 'nitro/vite'`, so the same text in a string is not an import. A string's text reaches the prerender check only when it is a key, so a route such as /docs/enabled:false is not read as `enabled: false`. `nitro(...) {` declares a function and is not counted as a call. --- .../src/Detection/Framework/TanStackStart.php | 34 ++++++++++++++++--- packages/detector/tests/DetectorTest.php | 3 ++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 69f3c6a816f..972739d6833 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -67,10 +67,11 @@ public function getAdapter(string $configContent): string } // `$code` empties string literals, so `'nitro()'` is never read as a call, and keeps whitespace only between two names. - // `$compact` keeps the text of strings but drops quotes, whitespace and quoted braces, so `"enabled": false` still reads as a key. + // `$compact` drops whitespace and quotes, and keeps a string's text only when it is a key, so `"enabled": false` still reads as one. $code = ''; $compact = ''; $quote = ''; + $text = ''; $gap = false; for ($i = 0; $i < \strlen($stripped); $i++) { $char = $stripped[$i]; @@ -80,8 +81,11 @@ public function getAdapter(string $configContent): string } elseif ($char === $quote || ($char === "\n" && $quote !== '`')) { $code .= $char; $quote = ''; - } elseif (!\in_array($char, ['{', '}'], true) && !\ctype_space($char)) { - $compact .= $char; + if (\str_starts_with(\ltrim(\substr($stripped, $i + 1)), ':')) { + $compact .= $text; + } + } else { + $text .= $char; } continue; @@ -102,6 +106,7 @@ public function getAdapter(string $configContent): string $code .= $char; if (\in_array($char, ['\'', '"', '`'], true)) { $quote = $char; + $text = ''; } else { $compact .= $char; } @@ -112,6 +117,10 @@ public function getAdapter(string $configContent): string foreach (['nitro/vite' => 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { for ($at = \strpos($stripped, $module); $at !== false; $at = \strpos($stripped, $module, $at + 1)) { $import = \substr($stripped, 0, $at); + if (!\in_array(\substr($import, -1), ['\'', '"'], true) || !\str_ends_with(\rtrim(\substr($import, 0, -1)), 'from')) { + continue; + } + $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', '}', ',', "\t", "\r", "\n"], ' ', \substr($import, (int) \strrpos($import, 'import ')))))); $index = \array_search($export, $names, true); $calls[] = match (true) { @@ -127,7 +136,24 @@ public function getAdapter(string $configContent): string foreach (\array_filter($calls) as $call) { for ($at = \strpos($code, $call . '('); $at !== false; $at = \strpos($code, $call . '(', $at + 1)) { $before = $at > 0 ? $code[$at - 1] : ' '; - if (!\ctype_alnum($before) && !\in_array($before, ['_', '$', '.'], true)) { + if (\ctype_alnum($before) || \in_array($before, ['_', '$', '.'], true)) { + continue; + } + + // `nitro(...) {` declares a function of that name rather than calling the plugin. + for ($end = $at + \strlen($call), $depth = 0; $end < \strlen($code); $end++) { + $depth += match ($code[$end]) { + '(' => 1, + ')' => -1, + default => 0, + }; + + if ($depth === 0) { + break; + } + } + + if (($code[$end + 1] ?? '') !== '{') { return 'ssr'; } } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 50cd58f5945..0be4d3fa545 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -806,6 +806,9 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitro ()] })')); $this->assertSame('ssr', $fw->getAdapter('import { nitro as unused } from \'nitro/vite\'' . "\n" . 'import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'}\'], enabled: false } })] })')); + $this->assertSame('static', $fw->getAdapter('import { nitro } from \'./local-plugin\'' . "\n" . 'export default defineConfig({ define: { source: \'nitro/vite\' }, plugins: [tanstackStart({ prerender: { enabled: true } }), nitro()] })')); + $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/docs/enabled:false\'] } })] })')); + $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'const hooks = { nitro () {} }' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); From 962f39cf3e3b0009bfa6f07d78ab4dd8683dcce5 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:15:57 +0530 Subject: [PATCH 444/470] refactor(detector): detect Nitro by its package, like SvelteKit Replaces the hand-rolled import and call parsing with the same check SvelteKit uses for adapter-static: the Nitro package in the comment-stripped config means ssr. Drops the enabled: false handling, which is outside #13912. --- .../src/Detection/Framework/TanStackStart.php | 113 +----------------- packages/detector/tests/DetectorTest.php | 18 +-- 2 files changed, 4 insertions(+), 127 deletions(-) diff --git a/packages/detector/src/Detection/Framework/TanStackStart.php b/packages/detector/src/Detection/Framework/TanStackStart.php index 972739d6833..72bea17ee54 100644 --- a/packages/detector/src/Detection/Framework/TanStackStart.php +++ b/packages/detector/src/Detection/Framework/TanStackStart.php @@ -62,119 +62,8 @@ public function getAdapter(string $configContent): string { $stripped = \preg_replace('/(? 'nitro', '@tanstack/nitro-v2-vite-plugin' => 'nitroV2Plugin'] as $module => $export) { - for ($at = \strpos($stripped, $module); $at !== false; $at = \strpos($stripped, $module, $at + 1)) { - $import = \substr($stripped, 0, $at); - if (!\in_array(\substr($import, -1), ['\'', '"'], true) || !\str_ends_with(\rtrim(\substr($import, 0, -1)), 'from')) { - continue; - } - - $names = \array_values(\array_filter(\explode(' ', \str_replace(['{', '}', ',', "\t", "\r", "\n"], ' ', \substr($import, (int) \strrpos($import, 'import ')))))); - $index = \array_search($export, $names, true); - $calls[] = match (true) { - ($names[1] ?? '') === '*' && isset($names[3]) => $names[3] . '.' . $export, - $index !== false && ($names[$index + 1] ?? '') === 'as' => $names[$index + 2] ?? '', - $index !== false => $export, - default => '', - }; - } - } - // Nitro emits `.output/server/index.mjs` even when every route is prerendered. - foreach (\array_filter($calls) as $call) { - for ($at = \strpos($code, $call . '('); $at !== false; $at = \strpos($code, $call . '(', $at + 1)) { - $before = $at > 0 ? $code[$at - 1] : ' '; - if (\ctype_alnum($before) || \in_array($before, ['_', '$', '.'], true)) { - continue; - } - - // `nitro(...) {` declares a function of that name rather than calling the plugin. - for ($end = $at + \strlen($call), $depth = 0; $end < \strlen($code); $end++) { - $depth += match ($code[$end]) { - '(' => 1, - ')' => -1, - default => 0, - }; - - if ($depth === 0) { - break; - } - } - - if (($code[$end + 1] ?? '') !== '{') { - return 'ssr'; - } - } - } - - // Cut at the brace that closes the block, so a `({ path }) =>` filter does not end it early. - $prerender = (string) \strstr($compact, 'prerender:{'); - for ($i = 10, $depth = 0; $i < \strlen($prerender); $i++) { - $depth += match ($prerender[$i]) { - '{' => 1, - '}' => -1, - default => 0, - }; - - if ($depth === 0) { - $prerender = \substr($prerender, 0, $i); - break; - } - } - - if (\str_contains($prerender, 'enabled:false')) { + if (\str_contains($stripped, 'nitro/vite') || \str_contains($stripped, '@tanstack/nitro-v2-vite-plugin')) { return 'ssr'; } diff --git a/packages/detector/tests/DetectorTest.php b/packages/detector/tests/DetectorTest.php index 0be4d3fa545..5e444dc9fa7 100644 --- a/packages/detector/tests/DetectorTest.php +++ b/packages/detector/tests/DetectorTest.php @@ -796,25 +796,13 @@ public function testTanStackStartAdapterDetection(): void $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart()] })')); $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/\'] } })] })')); - $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); - $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), nitroV2Plugin()] })')); - $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }) /*, nitro() */] })')); - $this->assertSame('ssr', $fw->getAdapter('import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); - $this->assertSame('ssr', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitroPlugin.nitro()] })')); - $this->assertSame('static', $fw->getAdapter('import * as nitroPlugin from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), settings.nitro()] })')); - $this->assertSame('static', $fw->getAdapter('import { nitro as n } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), paraglideVitePlugin()] })')); - $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitro ()] })')); - $this->assertSame('ssr', $fw->getAdapter('import { nitro as unused } from \'nitro/vite\'' . "\n" . 'import { nitro as serverPlugin } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), serverPlugin()] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'}\'], enabled: false } })] })')); - $this->assertSame('static', $fw->getAdapter('import { nitro } from \'./local-plugin\'' . "\n" . 'export default defineConfig({ define: { source: \'nitro/vite\' }, plugins: [tanstackStart({ prerender: { enabled: true } }), nitro()] })')); - $this->assertSame('static', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { routes: [\'/docs/enabled:false\'] } })] })')); - $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'const hooks = { nitro () {} }' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } })] })')); - $this->assertSame('static', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ define: { note: \'nitro()\' }, plugins: [tanstackStart({ prerender: { enabled: true } })] })')); - $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: { filter: ({ path }) => path !== \'/\', enabled: false } })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ prerender: false })] })')); $this->assertSame('ssr', $fw->getAdapter('export default defineConfig({ plugins: [tanstackStart({ "prerender": false })] })')); $this->assertSame('ssr', $fw->getAdapter('// prerender: true' . "\n" . 'export default defineConfig({})')); $this->assertSame('static', $fw->getAdapter('server: { url: "https://example.com" },' . "\n" . 'prerender: { routes: [\'/\'] }')); + $this->assertSame('ssr', $fw->getAdapter('import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart(), nitro({ prerender: { routes: [\'/\'], crawlLinks: true } })] })')); + $this->assertSame('ssr', $fw->getAdapter('import { nitroV2Plugin } from \'@tanstack/nitro-v2-vite-plugin\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } }), nitroV2Plugin()] })')); + $this->assertSame('static', $fw->getAdapter('// import { nitro } from \'nitro/vite\'' . "\n" . 'export default defineConfig({ plugins: [tanstackStart({ prerender: { enabled: true } })] })')); $this->assertNotEmpty($fw->getConfigFiles()); } From bd04773a641dbd8833477e5b13a60e4ebda0ee56 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:21:20 +0530 Subject: [PATCH 445/470] docs(flutter): explain that native OAuth2 must use the default redirect On Android, iOS and macOS the Flutter SDK only listens for the appwrite-callback-[PROJECT_ID] scheme, and the server only adds the session key and secret to the default success redirect. A custom success URL such as an https App Link therefore ends the login with PlatformException(CANCELED) or an invalid OAuth2 response even though the session was created. Tell readers to omit success and failure and navigate after the Future completes. --- docs/sdks/flutter/GETTING_STARTED.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sdks/flutter/GETTING_STARTED.md b/docs/sdks/flutter/GETTING_STARTED.md index 75cf9de1afc..a3ecf03422a 100644 --- a/docs/sdks/flutter/GETTING_STARTED.md +++ b/docs/sdks/flutter/GETTING_STARTED.md @@ -30,6 +30,8 @@ In order to capture the Appwrite OAuth callback url, the following activity need ``` +> On Android, iOS and macOS, call `createOAuth2Session` without the `success` and `failure` URLs. The SDK only returns to your app through the `appwrite-callback-[PROJECT_ID]` scheme, and only the default redirect carries the new session with it. A custom URL, such as an https App Link, makes the call fail (for example with `PlatformException(CANCELED)`) even when the login itself succeeded. Await the returned `Future` and navigate from your Dart code instead. + ### iOS For **iOS** first add your app name and Bundle ID, You can find your Bundle Identifier in the General tab for your app's primary target in Xcode. From ef032e0cb7185037c7c6d76e4d2ba9fcf4edc3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Sep 2026 11:55:47 +0200 Subject: [PATCH 446/470] test: harden JWT TOTP setup and push rotation assertions Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/Services/Account/AccountCustomClientTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 4097b1c1732..0574ffb884d 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -5543,6 +5543,9 @@ public function testMFAFactorCountUnderJWT(): void $this->assertEquals(200, $authenticator['headers']['status-code']); $totp = \OTPHP\TOTP::create($authenticator['body']['secret']); + if ($totp->expiresIn() <= 5) { + $this->getNextTOTP($totp, $totp->now()); + } $verification = $this->client->call(Client::METHOD_PUT, '/account/mfa/authenticators/totp', $headers, [ 'otp' => $totp->now(), ]); @@ -5657,6 +5660,13 @@ public function testCreatePushTargetReplacesRotatedTokenUnderJWT(): void ]); $this->assertEquals(201, $second['headers']['status-code']); $this->assertEquals($first['body']['$id'], $second['body']['$id']); + + $account = $this->client->call(Client::METHOD_GET, '/account', $jwtHeaders); + $this->assertEquals(200, $account['headers']['status-code']); + + $identifiers = \array_column($account['body']['targets'], 'identifier'); + $this->assertContains('jwt-identifier-after-rotation-' . $data['id'], $identifiers); + $this->assertNotContains('jwt-identifier-before-rotation-' . $data['id'], $identifiers); } public function testRefreshEmailPasswordSession(): void From 484b453991922ac3687b422bb9dc2622237cb1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Sep 2026 11:43:15 +0200 Subject: [PATCH 447/470] fix: resolve the current session under JWT auth The session resource only resolved from the cookie secret, so JWT requests had no session. MFA factor enforcement was skipped, MFA-protected routes crashed, and 'current' session lookups, current-session flags, password change invalidation and push target rotation all missed the JWT's session. Fall back to the JWT's session ID and use the session resource in those account routes. Co-Authored-By: Claude Opus 5.5 (1M context) --- app/controllers/api/account.php | 49 ++++++++++++++------------------- app/init/resources/request.php | 19 +++++++++++-- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ec54f2ad854..03c918137a2 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -600,22 +600,21 @@ ->inject('response') ->inject('targetUser') ->inject('locale') - ->inject('store') - ->inject('proofForToken') - ->action(function (Response $response, User $targetUser, Locale $locale, Store $store, ProofsToken $proofForToken) { + ->inject('session') + ->action(function (Response $response, User $targetUser, Locale $locale, ?Document $current) { $sessions = $targetUser->getAttribute('sessions', []); // While impersonating, the request runs on the impersonator's session, so none of // the target's sessions is marked current. - $current = $targetUser->sessionVerify($store->getProperty('secret', ''), $proofForToken); + $currentId = $current?->getId(); foreach ($sessions as $key => $session) { /** @var Document $session */ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session->setAttribute('countryName', $countryName); - $session->setAttribute('current', ($current == $session->getId()) ? true : false); + $session->setAttribute('current', $currentId === $session->getId()); $session->setAttribute('secret', $session->getAttribute('secret', '')); $sessions[$key] = $session; @@ -734,16 +733,15 @@ ->inject('response') ->inject('targetUser') ->inject('locale') - ->inject('store') - ->inject('proofForToken') - ->action(function (?string $sessionId, Response $response, User $targetUser, Locale $locale, Store $store, ProofsToken $proofForToken) { + ->inject('session') + ->action(function (?string $sessionId, Response $response, User $targetUser, Locale $locale, ?Document $current) { $sessions = $targetUser->getAttribute('sessions', []); // While impersonating, the request runs on the impersonator's session, so 'current' // resolves against none of the target's sessions and this throws. That matches the // sessions list, which marks none of them current for the same reason. $sessionId = ($sessionId === 'current') - ? $targetUser->sessionVerify($store->getProperty('secret', ''), $proofForToken) + ? $current?->getId() : $sessionId; foreach ($sessions as $session) { @@ -752,7 +750,7 @@ $countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session - ->setAttribute('current', ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret')))) + ->setAttribute('current', $session->getId() === $current?->getId()) ->setAttribute('countryName', $countryName) ->setAttribute('secret', $session->getAttribute('secret', '')) ; @@ -801,11 +799,12 @@ ->inject('proofForToken') ->inject('domainVerification') ->inject('cookieDomain') - ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) { + ->inject('session') + ->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, DeletePublisher $publisherForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, ?Document $current) { $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? $user->sessionVerify($store->getProperty('secret', ''), $proofForToken) + ? $current?->getId() : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -885,12 +884,11 @@ ->inject('dbForProject') ->inject('project') ->inject('queueForEvents') - ->inject('store') - ->inject('proofForToken') - ->action(function (?string $sessionId, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Store $store, ProofsToken $proofForToken) { + ->inject('session') + ->action(function (?string $sessionId, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, ?Document $current) { $sessionId = ($sessionId === 'current') - ? $user->sessionVerify($store->getProperty('secret', ''), $proofForToken) + ? $current?->getId() : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -3508,11 +3506,10 @@ ->inject('dbForProject') ->inject('queueForEvents') ->inject('hooks') - ->inject('store') ->inject('proofForPassword') - ->inject('proofForToken') ->inject('pwnedPasswords') - ->action(function (string $password, string $oldPassword, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, PasswordPwned $pwnedPasswords) { + ->inject('session') + ->action(function (string $password, string $oldPassword, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, PasswordPwned $pwnedPasswords, ?Document $current) { $userProofForPassword = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions')); // Check old password only if its an existing user. if (!empty($user->getAttribute('passwordUpdate')) && !$userProofForPassword->verify($oldPassword, $user->getAttribute('password'))) { // Double check user password @@ -3562,13 +3559,11 @@ $sessions = $user->getAttribute('sessions', []); - $current = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken); - $invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false; - if ($invalidate && !empty($current)) { + if ($invalidate && $current !== null) { foreach ($sessions as $session) { /** @var Document $session */ - if ($session->getId() !== $current) { + if ($session->getId() !== $current->getId()) { $dbForProject->deleteDocument('sessions', $session->getId()); } } @@ -5263,10 +5258,9 @@ ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('store') - ->inject('proofForToken') ->inject('authorization') - ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken, Authorization $authorization) { + ->inject('session') + ->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization, ?Document $current) { $targetId = $targetId == 'unique()' ? ID::unique() : $targetId; $provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId)); @@ -5282,8 +5276,7 @@ $device = $detector->getDevice(); - $sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken); - $session = $dbForProject->getDocument('sessions', $sessionId); + $session = $dbForProject->getDocument('sessions', $current?->getId() ?? ''); $name = "{$device['deviceBrand']} {$device['deviceModel']}"; // A session is one device install holding one push token per provider. Re-registering a rotated diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 86d10eb0cb6..09acccdcc92 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -647,7 +647,7 @@ return $project; }, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia', 'projectIdFromPath']); - $context->set('session', function (User $user, Store $store, Token $proofForToken) { + $context->set('session', function (User $user, Store $store, Token $proofForToken, Request $request) { if ($user->isEmpty()) { return; } @@ -655,6 +655,21 @@ $sessions = $user->getAttribute('sessions', []); $sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken); + $authJWT = $request->getHeaderLine('x-appwrite-jwt', ''); + if (! $sessionId && ! empty($authJWT)) { + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0); + try { + $payload = $jwt->decode($authJWT); + } catch (JWTException) { + return; + } + + $jwtSessionId = $payload['sessionId'] ?? ''; + if (($payload['userId'] ?? '') === $user->getId() && ! empty($jwtSessionId) && $user->sessionActive($jwtSessionId)) { + $sessionId = $jwtSessionId; + } + } + if (! $sessionId) { return; } @@ -666,7 +681,7 @@ } return; - }, ['user', 'store', 'proofForToken']); + }, ['user', 'store', 'proofForToken', 'request']); $context->set('pwnedPasswords', function (Cache $cache) { // Nothing is asked until an operator points this at a service From 861b73f2ce0856893e186d03fed9c824648dc741 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:31:41 +0530 Subject: [PATCH 448/470] fix(locale): correct misspelled link word in Arabic recovery email --- app/config/locale/translations/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/locale/translations/ar.json b/app/config/locale/translations/ar.json index df077c8685b..a0aa8621ad1 100644 --- a/app/config/locale/translations/ar.json +++ b/app/config/locale/translations/ar.json @@ -16,7 +16,7 @@ "emails.magicSession.signature": "فريق {{project}}", "emails.recovery.subject": "تغيير كلمة السر", "emails.recovery.hello": "أهلا {{user}}،", - "emails.recovery.body": "برجاء اتباع الراط التالي لتغيير كلمة السر الخاصة بـ{{project}}", + "emails.recovery.body": "برجاء اتباع الرابط التالي لتغيير كلمة السر الخاصة بـ{{project}}", "emails.recovery.footer": "لولم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة", "emails.recovery.thanks": "شكرا،", "emails.recovery.buttonText": "إعادة تعيين كلمة المرور", From 811241306f825aec3efed20a53dc9adb7f47d1c4 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:33:22 +0530 Subject: [PATCH 449/470] fix(vcs): skip deployments for repositories the resource no longer links to --- .../Platform/Modules/VCS/Http/GitHub/Deployment.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php index 55e0479a697..52d55eb0229 100644 --- a/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php +++ b/src/Appwrite/Platform/Modules/VCS/Http/GitHub/Deployment.php @@ -103,6 +103,13 @@ protected function createGitDeployments( Span::add("{$logBase}.build.skipped", 'true'); continue; } + + // Stale repository rows can outlive a disconnect, so only build for the repository the resource still links to. + if ($resource->getAttribute('repositoryId', '') !== $repositoryId) { + Span::add("{$logBase}.build.skipped.reason", 'repository not connected'); + Span::add("{$logBase}.build.skipped", 'true'); + continue; + } $resourceInternalId = $resource->getSequence(); $validator = new Contains(VCS_DEPLOYMENT_SKIP_PATTERNS); From b54b378b728168cfefdfe06282f6a141bae0efd2 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:38:38 +0530 Subject: [PATCH 450/470] fix(locale): add missing space in Arabic recovery email footer --- app/config/locale/translations/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/locale/translations/ar.json b/app/config/locale/translations/ar.json index a0aa8621ad1..2ac479d72e2 100644 --- a/app/config/locale/translations/ar.json +++ b/app/config/locale/translations/ar.json @@ -17,7 +17,7 @@ "emails.recovery.subject": "تغيير كلمة السر", "emails.recovery.hello": "أهلا {{user}}،", "emails.recovery.body": "برجاء اتباع الرابط التالي لتغيير كلمة السر الخاصة بـ{{project}}", - "emails.recovery.footer": "لولم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة", + "emails.recovery.footer": "لو لم تطلب تغيير كلمة السر، يمكنك تجاهل هذه الرسالة", "emails.recovery.thanks": "شكرا،", "emails.recovery.buttonText": "إعادة تعيين كلمة المرور", "emails.recovery.signature": "فريق {{project}}", From c60c1cfb5efe335098286806381bc904a457c2b6 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:45:01 +0530 Subject: [PATCH 451/470] fix(storage): clean up uploads when the antivirus scan cannot run An unreachable ClamAV daemon made scanInStream() throw, which escaped the scan handling entirely: the joined upload stayed on disk and a chunked upload kept its pending file document. With the chunks already joined, that document could never complete, and deleting it failed because the device had no chunk directory left to abort. A connection failure is now a failed scan, and any scan that is not clean removes both the finalized upload and the pending document before returning the error. --- .../Storage/Http/Buckets/Files/Create.php | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php index 61e5bc64028..064f521a07c 100644 --- a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php +++ b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Modules\Storage\Http\Buckets\Files; use Appwrite\ClamAV\Network; +use Appwrite\ClamAV\ScanResult; use Appwrite\Event\Event; use Appwrite\Extend\Exception; use Appwrite\OpenSSL\OpenSSL; @@ -370,16 +371,27 @@ public function action( (int) System::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310) ); - $scan = $antivirus->scanInStream($path); + try { + $scan = $antivirus->scanInStream($path); + } catch (\RuntimeException $e) { + $scan = ScanResult::failed($e->getMessage()); + } - if ($scan->isInfected()) { + if (!$scan->isClean()) { + // The finalized upload has no completed file record yet, and a pending + // one would be left pointing at chunks that are already joined. $deviceForFiles->delete($path); + + if (!$file->isEmpty()) { + $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId)); + } + } + + if ($scan->isInfected()) { throw new Exception(Exception::STORAGE_INVALID_FILE); } if ($scan->hasFailed()) { - // The finalized upload has no completed file record yet. - $deviceForFiles->delete($path); throw new Exception( Exception::GENERAL_SERVER_ERROR, 'Unable to scan the uploaded file: ' . $scan->getReply() From fadd0a8b9f4e86c8fa9f7b9dd11dece89fb82925 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:50:30 +0530 Subject: [PATCH 452/470] chore(deps): require utopia-php/database ^7.4.0 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 53cf04a5984..7fc947aa7c7 100644 --- a/composer.json +++ b/composer.json @@ -149,7 +149,7 @@ "appwrite/php-runtimes": "0.20.*", "appwrite/php-clamav": "2.0.*", "utopia-php/config": "1.*", - "utopia-php/database": "^7.3.11", + "utopia-php/database": "^7.4.0", "utopia-php/migration": "^2.0.0", "mustangostang/spyc": "0.6.*", "dragonmantank/cron-expression": "3.4.*", diff --git a/composer.lock b/composer.lock index c74fd83f85d..b41674ed294 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": "386999fdf620ccb4f4663f9dd69a7c5e", + "content-hash": "dd4622072c49f8b71053c2866ca7fc92", "packages": [ { "name": "adhocore/jwt", @@ -2827,16 +2827,16 @@ }, { "name": "utopia-php/database", - "version": "7.3.11", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "e45195ffb7019e70b88cf7d33814ecafd3fa2866" + "reference": "1c99c2179d13f79476a51dd3b73ecc0025058d14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/e45195ffb7019e70b88cf7d33814ecafd3fa2866", - "reference": "e45195ffb7019e70b88cf7d33814ecafd3fa2866", + "url": "https://api.github.com/repos/utopia-php/database/zipball/1c99c2179d13f79476a51dd3b73ecc0025058d14", + "reference": "1c99c2179d13f79476a51dd3b73ecc0025058d14", "shasum": "" }, "require": { @@ -2881,9 +2881,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/7.3.11" + "source": "https://github.com/utopia-php/database/tree/7.4.0" }, - "time": "2026-09-17T08:18:45+00:00" + "time": "2026-09-29T10:10:07+00:00" }, { "name": "utopia-php/migration", From 448f7d2fd4d8b5446a12a4ea8ba06fa335811e39 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:50:30 +0530 Subject: [PATCH 453/470] refactor(databases): publish related updates from the database's delete events The database now fires an update for each two-way related document a delete changes, after the delete commits. Collect those while deleting instead of walking the relationships and re-reading every related document afterwards. --- .../Collections/Documents/Delete.php | 108 +++++++++--------- 1 file changed, 52 insertions(+), 56 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index b0e1e69618f..cd5c52a5b57 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -195,6 +195,12 @@ public function action( return; } + // The database fires an update for each related document the delete changed + $related = []; + $dbForDatabases->on(Database::EVENT_DOCUMENT_UPDATE, 'relationship-delete', function (string $event, Document $document) use (&$related) { + $related[] = $document; + }); + try { $dbForDatabases->withRequestTimestamp($requestTimestamp, function () use ($dbForDatabases, $database, $collection, $documentId) { $dbForDatabases->deleteDocument( @@ -206,6 +212,8 @@ public function action( throw new Exception($this->getConflictException()); } catch (RestrictedException) { throw new Exception($this->getRestrictedException()); + } finally { + $dbForDatabases->on(Database::EVENT_DOCUMENT_UPDATE, 'relationship-delete', null); } $collectionsCache = []; @@ -248,9 +256,8 @@ public function action( $this->triggerRelationshipUpdates( database: $database, collection: $collection, - document: $document, + related: $related, dbForProject: $dbForProject, - dbForDatabases: $dbForDatabases, queueForEvents: $queueForEvents, queueForRealtime: $queueForRealtime, response: $response, @@ -261,23 +268,26 @@ public function action( } /** - * Deleting a document changes two-way relationships on the documents that survive it, - * but the database applies those changes without events, so publish an update for each of them. + * Publish an update for each related document the delete changed + * + * @param array $related */ private function triggerRelationshipUpdates( Document $database, Document $collection, - Document $document, + array $related, Database $dbForProject, - Database $dbForDatabases, Event $queueForEvents, Realtime $queueForRealtime, UtopiaResponse $response, Authorization $authorization ): void { - $processed = []; - $collectionsCache = []; + if (empty($related)) { + return; + } + // Related documents carry the internal collection id, so map it back through the two-way relationships + $relatedCollections = []; foreach ($collection->getAttribute('attributes', []) as $attribute) { if ( $attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP @@ -286,17 +296,25 @@ private function triggerRelationshipUpdates( continue; } - $related = $document->getAttribute($attribute->getAttribute('key')); - $relations = \is_array($related) ? $related : [$related]; - $relatedCollectionId = $attribute->getAttribute('relatedCollection'); $relatedCollection = $authorization->skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence(), - $relatedCollectionId + $attribute->getAttribute('relatedCollection') )); if ($relatedCollection->isEmpty()) { continue; } + $relatedCollections['database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence()] = $relatedCollection; + } + + $collectionsCache = []; + + foreach ($related as $peer) { + $relatedCollection = $relatedCollections[$peer->getCollection()] ?? null; + if ($relatedCollection === null) { + continue; + } + $sensitive = \array_map( fn (Document $attr) => $attr->getAttribute('key'), \array_filter( @@ -305,51 +323,29 @@ private function triggerRelationshipUpdates( ) ); - foreach ($relations as $relation) { - if ( - !$relation instanceof Document - || $relation->isEmpty() - || isset($processed[$relatedCollectionId][$relation->getId()]) - ) { - continue; - } - $processed[$relatedCollectionId][$relation->getId()] = true; - - // Relationship keys are stripped from the payload, so don't load them. - $peer = $authorization->skip(fn () => $dbForDatabases->skipRelationships(fn () => $dbForDatabases->getDocument( - 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), - $relation->getId() - ))); - - // Cascaded away by this delete. - if ($peer->isEmpty()) { - continue; - } - - $this->processDocument( - database: $database, - collection: $relatedCollection, - document: $peer, - dbForProject: $dbForProject, - collectionsCache: $collectionsCache, - authorization: $authorization - ); + $this->processDocument( + database: $database, + collection: $relatedCollection, + document: $peer, + dbForProject: $dbForProject, + collectionsCache: $collectionsCache, + authorization: $authorization + ); - // Clone so the delete event stays intact for the shutdown hook. - $event = clone $queueForEvents; - $event->reset() - ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].update') - ->setParam('databaseId', $database->getId()) - ->setParam('collectionId', $relatedCollectionId) - ->setParam('tableId', $relatedCollectionId) - ->setParam('documentId', $peer->getId()) - ->setParam('rowId', $peer->getId()) - ->setContext($this->getCollectionsEventsContext(), $relatedCollection) - // Filter through the model directly; output() would replace the audited response payload. - ->setPayload($response->getModel($this->getResponseModel())->filter($peer)->getArrayCopy(), sensitive: $sensitive); - - $queueForRealtime->from($event)->trigger(); - } + // Clone so the delete event stays intact for the shutdown hook. + $event = clone $queueForEvents; + $event->reset() + ->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].update') + ->setParam('databaseId', $database->getId()) + ->setParam('collectionId', $relatedCollection->getId()) + ->setParam('tableId', $relatedCollection->getId()) + ->setParam('documentId', $peer->getId()) + ->setParam('rowId', $peer->getId()) + ->setContext($this->getCollectionsEventsContext(), $relatedCollection) + // Filter through the model directly; output() would replace the audited response payload. + ->setPayload($response->getModel($this->getResponseModel())->filter($peer)->getArrayCopy(), sensitive: $sensitive); + + $queueForRealtime->from($event)->trigger(); } } } From 97ce8baddcf2faad54ed0fb219f026414e65f6d3 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:51:35 +0530 Subject: [PATCH 454/470] fix(storage): let local abort succeed once chunks are joined A pending file whose chunks were already joined has no chunk directory left, so Local::abort() returned false from rmdir() and deleting the file failed with "Failed to delete file from device". Uploads left pending by an earlier failed antivirus scan, or any other failure after the join, could never be removed. With no chunk directory there is nothing left to abort, so abort() now returns true after removing the joined file. --- packages/storage/src/Device/Local.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/storage/src/Device/Local.php b/packages/storage/src/Device/Local.php index 70cb90a70d1..4525b9d9f89 100644 --- a/packages/storage/src/Device/Local.php +++ b/packages/storage/src/Device/Local.php @@ -221,6 +221,12 @@ public function abort(string $path, string $uploadId = ''): bool if (! file_exists(\dirname($tmp))) { // Checks if directory path to file exists throw new NotFoundException('File doesn\'t exist: ' . \dirname($path)); } + + // Chunks that were already joined into the file leave no directory behind. + if (! file_exists($tmp)) { + return true; + } + $files = $this->scanDirectory($tmp); foreach ($files as $file) { From 7b9b03177e110a61ebb715dec0f60bc9e994d9fe Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:54:05 +0530 Subject: [PATCH 455/470] fix(databases): keep a completed delete successful when related updates fail to publish --- .../Collections/Documents/Delete.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index cd5c52a5b57..6c3279c83b8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -14,6 +14,7 @@ use Appwrite\Usage\Context; use Appwrite\Utopia\Database\Documents\User; use Appwrite\Utopia\Response as UtopiaResponse; +use Utopia\Console; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Conflict as ConflictException; @@ -253,16 +254,21 @@ public function action( ->setContext($this->getCollectionsEventsContext(), $collection) ->setPayload($response->output($document, $this->getResponseModel()), sensitive: $relationships); - $this->triggerRelationshipUpdates( - database: $database, - collection: $collection, - related: $related, - dbForProject: $dbForProject, - queueForEvents: $queueForEvents, - queueForRealtime: $queueForRealtime, - response: $response, - authorization: $authorization - ); + // The delete already happened, so failing to notify related documents must not fail it + try { + $this->triggerRelationshipUpdates( + database: $database, + collection: $collection, + related: $related, + dbForProject: $dbForProject, + queueForEvents: $queueForEvents, + queueForRealtime: $queueForRealtime, + response: $response, + authorization: $authorization + ); + } catch (\Throwable $e) { + Console::warning('Failed to publish relationship updates: ' . $e->getMessage()); + } $response->noContent(); } From dc3a88e4f3716404db76b2d4a85ab419f70c79a5 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:54:05 +0530 Subject: [PATCH 456/470] test(realtime): assert the surviving document's update payload --- tests/e2e/Services/Realtime/RealtimeCustomClientTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 219496b387b..80eff2a4088 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -4307,12 +4307,15 @@ public function testChannelDatabaseRelationshipDelete(): void ], $this->getHeaders())); $this->assertEquals(204, $response['headers']['status-code']); - $this->receiveUntilEvent( + $event = $this->receiveUntilEvent( $client, fn (array $message): bool => \in_array($channel . '.update', $message['data']['events'] ?? [], true), timeoutMs: 10000 ); + $this->assertEquals($ids[$survivor], $event['data']['payload']['$id']); + $this->assertArrayNotHasKey($survivor === 'parent' ? $key : $twoWayKey, $event['data']['payload']); + $client->close(); } } From 2895ed2624b2393b29f3096bbc187931ab6911b2 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 15:57:52 +0530 Subject: [PATCH 457/470] chore(deps): bump utopia-php/migration to 2.0.8 2.0.8 adds webflow to the OAuth2 provider allow-list. Without it every Appwrite migration of auth settings reports an error for webflow instead of skipping it, which fails MigrationsConsoleClientTest. --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index c74fd83f85d..f06d88a0dc2 100644 --- a/composer.lock +++ b/composer.lock @@ -2887,16 +2887,16 @@ }, { "name": "utopia-php/migration", - "version": "2.0.7", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "a702ce80d0073983df6d51769d82be006a69fa1f" + "reference": "1cacecb310251f7a7abf15009680a2f5b8991f80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/a702ce80d0073983df6d51769d82be006a69fa1f", - "reference": "a702ce80d0073983df6d51769d82be006a69fa1f", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/1cacecb310251f7a7abf15009680a2f5b8991f80", + "reference": "1cacecb310251f7a7abf15009680a2f5b8991f80", "shasum": "" }, "require": { @@ -2936,9 +2936,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/2.0.7" + "source": "https://github.com/utopia-php/migration/tree/2.0.8" }, - "time": "2026-09-08T12:03:52+00:00" + "time": "2026-09-29T10:25:37+00:00" }, { "name": "utopia-php/mongo", From dd3e5a9050befd0b6c2924c0c0c05ff46b8a479f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 29 Sep 2026 12:30:34 +0200 Subject: [PATCH 458/470] fix: mark the JWT's deleted session as current in the delete event Co-Authored-By: Claude Opus 5.5 (1M context) --- app/controllers/api/account.php | 10 +++---- .../WebhooksCustomClientTest.php | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 03c918137a2..c465021ec41 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -819,13 +819,13 @@ unset($sessions[$key]); - $session->setAttribute('current', false); + $session->setAttribute('current', $session->getId() === $current?->getId()); - if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too - $session - ->setAttribute('current', true) - ->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); + if ($session->getAttribute('current')) { + $session->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'))); + } + if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // If current session delete the cookies too if (!$domainVerification) { $response->addHeader('X-Fallback-Cookies', \json_encode([])); } diff --git a/tests/e2e/Services/ProjectWebhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/ProjectWebhooks/WebhooksCustomClientTest.php index 7ed5edfad19..5dd9c6b119d 100644 --- a/tests/e2e/Services/ProjectWebhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/ProjectWebhooks/WebhooksCustomClientTest.php @@ -355,6 +355,33 @@ public function testDeleteAccountSession(): void $this->assertEquals(true, $webhook['data']['current']); } + public function testDeleteAccountSessionCurrentUnderJWT(): void + { + $data = $this->setupAccountWithSession(); + $projectId = $this->getProject()['$id']; + + $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $data['session'], + ]); + $this->assertEquals(201, $jwt['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-jwt' => $jwt['body']['jwt'], + ]); + $this->assertEquals(204, $response['headers']['status-code']); + + $webhook = $this->getLastRequest($this->webhookEventProbe("users.{$data['id']}.sessions.{$data['sessionId']}.delete")); + $this->assertEquals($data['sessionId'], $webhook['data']['$id']); + $this->assertEquals(true, $webhook['data']['current']); + $this->assertIsString($webhook['data']['countryName']); + } + public function testDeleteAccountSessions(): void { // Set up account with session From b50fe339c2e81d16f58afbaa41b3481d013463f9 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 11:38:20 +0100 Subject: [PATCH 459/470] fix(storage): retry S3 internal errors and replayable transport failures Backblaze B2 answers failed parts and completions with 500 InternalError ("internal incident"), which the strategy treated as permanent, so one flaky request failed a multi-hour backup. Retry InternalError and unparseable 500/502/504, honour Retry-After, and retry transport failures that cannot apply a request twice: anything that never reached the service, and dropped or timed-out non-POST requests. --- .../storage/src/Device/S3/RetryStrategy.php | 71 +++++++++++++--- .../tests/Device/S3/RetryStrategyTest.php | 80 ++++++++++++++++--- packages/storage/tests/Device/S3Test.php | 61 ++++++++++++++ 3 files changed, 190 insertions(+), 22 deletions(-) diff --git a/packages/storage/src/Device/S3/RetryStrategy.php b/packages/storage/src/Device/S3/RetryStrategy.php index 6711fdfd9e8..70d2657d26c 100644 --- a/packages/storage/src/Device/S3/RetryStrategy.php +++ b/packages/storage/src/Device/S3/RetryStrategy.php @@ -9,18 +9,32 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Utopia\Client\Decorator\Retry\Strategy; +use Utopia\Client\Exception\ConnectionException; +use Utopia\Client\Exception\DnsException; +use Utopia\Client\Exception\TimeoutException; +use Utopia\Client\Exception\TlsException; +use Utopia\Psr7\Header; +use Utopia\Psr7\Method; /** - * Retry strategy for transient S3 rate-limiting errors (e.g. SlowDown, - * ServiceUnavailable), for use with the `utopia-php/client` Retry decorator. + * Retry strategy for transient S3 failures, for use with the + * `utopia-php/client` Retry decorator. * - * The XML body is parsed first so that specific S3 error codes are detected - * regardless of HTTP status: a 503/429 carrying a parseable but non-transient - * error code is not retried, while unparseable 429/503 responses fall back to - * status-code detection. + * A response is retried when the service reports it is throttled or failed + * internally (SlowDown, InternalError, ...). The XML body is parsed first so a + * specific error code wins over the HTTP status: a 5xx carrying a parseable but + * non-transient code is not retried, while unparseable 429/5xx responses fall + * back to status-code detection. + * + * A transport failure is retried when the request never reached the service + * (unresolvable host, refused connection), or when it dropped or timed out and + * replaying it cannot apply it twice. S3 writes with PUT to a fixed key or part + * number, so a replayed PUT overwrites itself; a replayed POST may create a + * second multipart upload, so it is not. TLS, proxy and protocol failures point + * at configuration rather than a passing fault and are not retried. * * Waits use exponential backoff with full jitter so a fleet throttled at the - * same moment does not retry in lockstep. + * same moment does not retry in lockstep. A numeric Retry-After is honoured. * @see \Utopia\Storage\Tests\Device\S3\RetryStrategyTest */ final readonly class RetryStrategy implements Strategy @@ -28,19 +42,19 @@ /** * @var array */ - private const array TRANSIENT_ERROR_CODES = ['SlowDown', 'ServiceUnavailable', 'Throttling', 'RequestThrottled']; + private const array TRANSIENT_ERROR_CODES = ['SlowDown', 'ServiceUnavailable', 'Throttling', 'RequestThrottled', 'InternalError']; /** * @var array */ - private const array TRANSIENT_STATUS_CODES = [429, 503]; + private const array TRANSIENT_STATUS_CODES = [429, 500, 502, 503, 504]; private Closure $randomizer; /** * @param int $retries Retries after the initial attempt * @param float $delay Base delay in seconds; the wait before retry N is drawn uniformly from [0, min(maxDelay, delay * 2^(N-1))) - * @param float $maxDelay Ceiling for the backoff window in seconds + * @param float $maxDelay Ceiling for the backoff window and for Retry-After, in seconds * @param (Closure(): float)|null $randomizer Returns a value in [0, 1) for jitter */ public function __construct( @@ -54,15 +68,19 @@ public function __construct( public function delay(RequestInterface $request, int $attempt, ?ResponseInterface $response, ?ClientExceptionInterface $error): ?float { - if ($attempt > $this->retries || ! $response instanceof ResponseInterface) { + if ($attempt > $this->retries) { return null; } - if (! $this->isTransient($response)) { + $transient = $response instanceof ResponseInterface + ? $this->isTransient($response) + : $this->isReplayable($request, $error); + + if (! $transient) { return null; } - return ($this->randomizer)() * min($this->maxDelay, $this->delay * 2 ** ($attempt - 1)); + return $this->retryAfter($response) ?? ($this->randomizer)() * min($this->maxDelay, $this->delay * 2 ** ($attempt - 1)); } private function isTransient(ResponseInterface $response): bool @@ -87,4 +105,31 @@ private function isTransient(ResponseInterface $response): bool // Fall back to HTTP status code for responses that cannot be parsed as XML. return \in_array($response->getStatusCode(), self::TRANSIENT_STATUS_CODES, true); } + + private function isReplayable(RequestInterface $request, ?ClientExceptionInterface $error): bool + { + // Nothing was sent: the host did not resolve or refused the connection. + if ($error instanceof DnsException || ($error instanceof ConnectionException && $error->getCode() === \CURLE_COULDNT_CONNECT)) { + return true; + } + + // A TLS failure is a ConnectionException too, but points at configuration. + if ($error instanceof TlsException) { + return false; + } + + // The request may have been applied before the connection dropped or timed out. + return ($error instanceof ConnectionException || $error instanceof TimeoutException) && $request->getMethod() !== Method::POST; + } + + private function retryAfter(?ResponseInterface $response): ?float + { + $value = $response?->getHeaderLine(Header::RETRY_AFTER) ?? ''; + + if (! is_numeric($value)) { + return null; + } + + return min($this->maxDelay, max(0.0, (float) $value)); + } } diff --git a/packages/storage/tests/Device/S3/RetryStrategyTest.php b/packages/storage/tests/Device/S3/RetryStrategyTest.php index 32319ad6520..dee0f71d939 100644 --- a/packages/storage/tests/Device/S3/RetryStrategyTest.php +++ b/packages/storage/tests/Device/S3/RetryStrategyTest.php @@ -5,7 +5,10 @@ namespace Utopia\Storage\Tests\Device\S3; use PHPUnit\Framework\TestCase; -use Utopia\Client\Exception\NetworkException; +use Utopia\Client\Exception\ConnectionException; +use Utopia\Client\Exception\DnsException; +use Utopia\Client\Exception\TimeoutException; +use Utopia\Client\Exception\TlsException; use Utopia\Psr7\Request; use Utopia\Psr7\Response; use Utopia\Psr7\Stream; @@ -14,9 +17,9 @@ final class RetryStrategyTest extends TestCase { - private function request(): Request + private function request(string $method = 'PUT'): Request { - return new Request('PUT', Uri::parse('https://s3.example.com/root/file.txt')); + return new Request($method, Uri::parse('https://s3.example.com/root/file.txt')); } private function response(int $status, string $body = ''): Response @@ -57,18 +60,37 @@ public function testStatusFallbackIsTransient(): void { $strategy = new RetryStrategy(); - $this->assertNotNull($strategy->delay($this->request(), 1, $this->response(429), null)); - $this->assertNotNull($strategy->delay($this->request(), 1, $this->response(503), null)); + foreach ([429, 500, 502, 503, 504] as $status) { + $this->assertNotNull($strategy->delay($this->request(), 1, $this->response($status), null), "HTTP {$status}"); + } + $this->assertNull($strategy->delay($this->request(), 1, $this->response(501), null)); + } + + /** Backblaze B2 answers a failed part or completion with InternalError and the message "internal incident". */ + public function testInternalErrorIsRetried(): void + { + $body = 'InternalErrorinternal incident'; + + $this->assertNotNull(new RetryStrategy()->delay($this->request('POST'), 1, $this->response(500, $body), null)); } /** XML error code takes precedence over HTTP status — 503 with non-transient XML must not be retried. */ public function test503WithNonTransientXmlIsNotRetried(): void { - $body = 'InternalErrorInternal server error.'; + $body = 'AccessDeniedAccess denied.'; $this->assertNull(new RetryStrategy()->delay($this->request(), 1, $this->response(503, $body), null)); } + public function testRetryAfterIsHonouredWithinTheCeiling(): void + { + $strategy = new RetryStrategy(delay: 0.5, maxDelay: 5.0, randomizer: static fn (): float => 1.0); + + $this->assertEqualsWithDelta(2.0, $strategy->delay($this->request(), 1, $this->response(503)->withHeader('Retry-After', '2'), null), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(5.0, $strategy->delay($this->request(), 1, $this->response(503)->withHeader('Retry-After', '120'), null), PHP_FLOAT_EPSILON); + $this->assertEqualsWithDelta(0.5, $strategy->delay($this->request(), 1, $this->response(503)->withHeader('Retry-After', 'Wed, 21 Oct 2026 07:28:00 GMT'), null), PHP_FLOAT_EPSILON, 'an HTTP-date falls back to backoff'); + } + public function testRetriesAreCapped(): void { $strategy = new RetryStrategy(retries: 2); @@ -79,11 +101,51 @@ public function testRetriesAreCapped(): void $this->assertNull($strategy->delay($this->request(), 3, $response, null)); } - public function testTransportErrorsAreNotRetried(): void + /** Nothing reached the service, so even a POST cannot have been applied. */ + public function testFailuresBeforeSendingAreRetriedForEveryMethod(): void + { + $strategy = new RetryStrategy(); + $post = $this->request('POST'); + + $this->assertNotNull($strategy->delay($post, 1, null, new ConnectionException($post, 'Could not connect to server', \CURLE_COULDNT_CONNECT))); + $this->assertNotNull($strategy->delay($post, 1, null, new DnsException($post, 'Could not resolve host', \CURLE_COULDNT_RESOLVE_HOST))); + } + + /** A PUT to the same key or part number overwrites itself, so replaying one that may have landed is harmless. */ + public function testDroppedOrTimedOutPutIsRetried(): void + { + $strategy = new RetryStrategy(); + $put = $this->request('PUT'); + + $this->assertNotNull($strategy->delay($put, 1, null, new ConnectionException($put, 'Connection reset by peer', \CURLE_RECV_ERROR))); + $this->assertNotNull($strategy->delay($put, 1, null, new TimeoutException($put, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); + } + + /** A POST that may have landed could open a second multipart upload, so it is not replayed. */ + public function testDroppedOrTimedOutPostIsNotRetried(): void + { + $strategy = new RetryStrategy(); + $post = $this->request('POST'); + + $this->assertNull($strategy->delay($post, 1, null, new ConnectionException($post, 'Connection reset by peer', \CURLE_RECV_ERROR))); + $this->assertNull($strategy->delay($post, 1, null, new TimeoutException($post, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); + } + + public function testTlsFailureIsNotRetried(): void + { + $put = $this->request('PUT'); + + $this->assertNull(new RetryStrategy()->delay($put, 1, null, new TlsException($put, 'SSL connect error', \CURLE_SSL_CONNECT_ERROR))); + } + + public function testTransportRetriesAreCapped(): void { - $error = new NetworkException($this->request(), 'Connection reset'); + $put = $this->request('PUT'); + $error = new TimeoutException($put, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT); + $strategy = new RetryStrategy(retries: 1); - $this->assertNull(new RetryStrategy()->delay($this->request(), 1, null, $error)); + $this->assertNotNull($strategy->delay($put, 1, null, $error)); + $this->assertNull($strategy->delay($put, 2, null, $error)); } public function testReadingTheBodyLeavesItReadable(): void diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index ab4eaa5a26a..9d74d64419f 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -12,6 +12,7 @@ use Utopia\Client\Adapter; use Utopia\Client\Decorator\Retry; use Utopia\Client\Exception\NetworkException; +use Utopia\Client\Exception\TimeoutException; use Utopia\Client\Redirect; use Utopia\Client\Tls; use Utopia\Psr7\Request; @@ -395,6 +396,66 @@ public function testTransientErrorRetriesAreExhausted(): void $this->assertCount(4, $client->requests); } + private function internalIncident(): Response + { + $body = 'InternalErrorinternal incident'; + + return new Response(500, body: new Stream($body))->withHeader('content-type', 'application/xml'); + } + + /** A B2 "internal incident" on a part or on the completion no longer fails the whole upload. */ + public function testMultipartUploadSurvivesInternalErrors(): void + { + $client = new ScriptedClient([ + new Response(200, body: new Stream('upload-1'))->withHeader('content-type', 'application/xml'), + new Response(200)->withHeader('etag', '"etag-1"'), + $this->internalIncident(), + new Response(200)->withHeader('etag', '"etag-2"'), + $this->internalIncident(), + new Response(200, body: new Stream('"etag-final"'))->withHeader('content-type', 'application/xml'), + ]); + $device = $this->device($client); + + $metadata = []; + $device->prepare('/root/archive.tar.gz', 'application/gzip', 2, $metadata); + $device->upload(new Stream('first'), '/root/archive.tar.gz', 'application/gzip', 1, 2, $metadata); + + // The last chunk completes the upload. + $this->assertSame(2, $device->upload(new Stream('second'), '/root/archive.tar.gz', 'application/gzip', 2, 2, $metadata)); + $this->assertCount(6, $client->requests); + $this->assertSame('second', (string) $client->requests[3]->getBody(), 'the retried part carries its whole body again'); + $this->assertStringContainsString('"etag-2"2', (string) $client->requests[5]->getBody()); + } + + public function testTimedOutPartIsReplayed(): void + { + $client = new ScriptedClient([ + new TimeoutException(new Request('PUT', Uri::parse('https://s3.example.com/root/file.txt')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), + new Response(200)->withHeader('etag', '"abc"'), + ]); + + $this->assertSame('abc', $this->device($client)->write('/root/file.txt', new Stream('Hello World'), 'text/plain')); + $this->assertCount(2, $client->requests); + $this->assertSame('Hello World', (string) $client->requests[1]->getBody()); + } + + public function testTimedOutMultipartCreationIsNotReplayed(): void + { + $client = new ScriptedClient([ + new TimeoutException(new Request('POST', Uri::parse('https://s3.example.com/root/file.txt')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), + ]); + + $metadata = []; + try { + $this->device($client)->prepare('/root/file.txt', 'text/plain', 2, $metadata); + self::fail('Expected the timeout to surface'); + } catch (TransportException) { + } + + $this->assertCount(1, $client->requests, 'a second POST could open a second multipart upload'); + $this->assertArrayNotHasKey('uploadId', $metadata); + } + public function testNoSuchKeyBecomesNotFoundException(): void { $body = 'NoSuchKeyThe specified key does not exist.'; From c9bcea83a688cc7a5996907edd84cf61e4c1755c Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 16:10:50 +0530 Subject: [PATCH 460/470] docs(flutter): move the OAuth redirect note above the platform sections It applies to Android, iOS and macOS, so it belongs where readers of all three see it. --- docs/sdks/flutter/GETTING_STARTED.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sdks/flutter/GETTING_STARTED.md b/docs/sdks/flutter/GETTING_STARTED.md index a3ecf03422a..6d419c5dd1d 100644 --- a/docs/sdks/flutter/GETTING_STARTED.md +++ b/docs/sdks/flutter/GETTING_STARTED.md @@ -7,6 +7,8 @@ From the options, choose to add a new **Flutter** platform and add your app cred If you are building your Flutter application for multiple devices, you have to follow this process for each different device. +> On Android, iOS and macOS, call `createOAuth2Session` without the `success` and `failure` URLs. The SDK only returns to your app through the `appwrite-callback-[PROJECT_ID]` scheme, and only the default redirect carries the new session with it. A custom URL, such as an https App Link, makes the call fail (for example with `PlatformException(CANCELED)`) even when the login itself succeeded. Await the returned `Future` and navigate from your Dart code instead. + ### Android For **Android** first add your app name and package name, Your package name is generally the **applicationId** in your app-level build.gradle file. By registering your new app platform, you are allowing your app to communicate with the Appwrite API. @@ -30,8 +32,6 @@ In order to capture the Appwrite OAuth callback url, the following activity need ``` -> On Android, iOS and macOS, call `createOAuth2Session` without the `success` and `failure` URLs. The SDK only returns to your app through the `appwrite-callback-[PROJECT_ID]` scheme, and only the default redirect carries the new session with it. A custom URL, such as an https App Link, makes the call fail (for example with `PlatformException(CANCELED)`) even when the login itself succeeded. Await the returned `Future` and navigate from your Dart code instead. - ### iOS For **iOS** first add your app name and Bundle ID, You can find your Bundle Identifier in the General tab for your app's primary target in Xcode. From 9ca321f5db6ebe87f1e5e0a77cb3c1edd8e2588a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 11:41:02 +0100 Subject: [PATCH 461/470] docs(storage): trim retry strategy comments --- .../storage/src/Device/S3/RetryStrategy.php | 26 +++++-------------- .../tests/Device/S3/RetryStrategyTest.php | 4 --- packages/storage/tests/Device/S3Test.php | 6 ++--- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/packages/storage/src/Device/S3/RetryStrategy.php b/packages/storage/src/Device/S3/RetryStrategy.php index 70d2657d26c..d885d1cfe98 100644 --- a/packages/storage/src/Device/S3/RetryStrategy.php +++ b/packages/storage/src/Device/S3/RetryStrategy.php @@ -20,21 +20,10 @@ * Retry strategy for transient S3 failures, for use with the * `utopia-php/client` Retry decorator. * - * A response is retried when the service reports it is throttled or failed - * internally (SlowDown, InternalError, ...). The XML body is parsed first so a - * specific error code wins over the HTTP status: a 5xx carrying a parseable but - * non-transient code is not retried, while unparseable 429/5xx responses fall - * back to status-code detection. - * - * A transport failure is retried when the request never reached the service - * (unresolvable host, refused connection), or when it dropped or timed out and - * replaying it cannot apply it twice. S3 writes with PUT to a fixed key or part - * number, so a replayed PUT overwrites itself; a replayed POST may create a - * second multipart upload, so it is not. TLS, proxy and protocol failures point - * at configuration rather than a passing fault and are not retried. - * - * Waits use exponential backoff with full jitter so a fleet throttled at the - * same moment does not retry in lockstep. A numeric Retry-After is honoured. + * The XML error code wins over the HTTP status; unparseable 429/5xx responses + * fall back to the status. Transport failures are retried only when replaying + * cannot apply the request twice. Waits use exponential backoff with full + * jitter, or a numeric Retry-After. * @see \Utopia\Storage\Tests\Device\S3\RetryStrategyTest */ final readonly class RetryStrategy implements Strategy @@ -54,7 +43,7 @@ /** * @param int $retries Retries after the initial attempt * @param float $delay Base delay in seconds; the wait before retry N is drawn uniformly from [0, min(maxDelay, delay * 2^(N-1))) - * @param float $maxDelay Ceiling for the backoff window and for Retry-After, in seconds + * @param float $maxDelay Ceiling for the backoff window in seconds * @param (Closure(): float)|null $randomizer Returns a value in [0, 1) for jitter */ public function __construct( @@ -108,17 +97,16 @@ private function isTransient(ResponseInterface $response): bool private function isReplayable(RequestInterface $request, ?ClientExceptionInterface $error): bool { - // Nothing was sent: the host did not resolve or refused the connection. + // Nothing was sent. if ($error instanceof DnsException || ($error instanceof ConnectionException && $error->getCode() === \CURLE_COULDNT_CONNECT)) { return true; } - // A TLS failure is a ConnectionException too, but points at configuration. if ($error instanceof TlsException) { return false; } - // The request may have been applied before the connection dropped or timed out. + // A replayed POST may open a second multipart upload. return ($error instanceof ConnectionException || $error instanceof TimeoutException) && $request->getMethod() !== Method::POST; } diff --git a/packages/storage/tests/Device/S3/RetryStrategyTest.php b/packages/storage/tests/Device/S3/RetryStrategyTest.php index dee0f71d939..fb272ef2faf 100644 --- a/packages/storage/tests/Device/S3/RetryStrategyTest.php +++ b/packages/storage/tests/Device/S3/RetryStrategyTest.php @@ -66,7 +66,6 @@ public function testStatusFallbackIsTransient(): void $this->assertNull($strategy->delay($this->request(), 1, $this->response(501), null)); } - /** Backblaze B2 answers a failed part or completion with InternalError and the message "internal incident". */ public function testInternalErrorIsRetried(): void { $body = 'InternalErrorinternal incident'; @@ -101,7 +100,6 @@ public function testRetriesAreCapped(): void $this->assertNull($strategy->delay($this->request(), 3, $response, null)); } - /** Nothing reached the service, so even a POST cannot have been applied. */ public function testFailuresBeforeSendingAreRetriedForEveryMethod(): void { $strategy = new RetryStrategy(); @@ -111,7 +109,6 @@ public function testFailuresBeforeSendingAreRetriedForEveryMethod(): void $this->assertNotNull($strategy->delay($post, 1, null, new DnsException($post, 'Could not resolve host', \CURLE_COULDNT_RESOLVE_HOST))); } - /** A PUT to the same key or part number overwrites itself, so replaying one that may have landed is harmless. */ public function testDroppedOrTimedOutPutIsRetried(): void { $strategy = new RetryStrategy(); @@ -121,7 +118,6 @@ public function testDroppedOrTimedOutPutIsRetried(): void $this->assertNotNull($strategy->delay($put, 1, null, new TimeoutException($put, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); } - /** A POST that may have landed could open a second multipart upload, so it is not replayed. */ public function testDroppedOrTimedOutPostIsNotRetried(): void { $strategy = new RetryStrategy(); diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index 9d74d64419f..24e273b0e80 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -403,7 +403,6 @@ private function internalIncident(): Response return new Response(500, body: new Stream($body))->withHeader('content-type', 'application/xml'); } - /** A B2 "internal incident" on a part or on the completion no longer fails the whole upload. */ public function testMultipartUploadSurvivesInternalErrors(): void { $client = new ScriptedClient([ @@ -420,10 +419,9 @@ public function testMultipartUploadSurvivesInternalErrors(): void $device->prepare('/root/archive.tar.gz', 'application/gzip', 2, $metadata); $device->upload(new Stream('first'), '/root/archive.tar.gz', 'application/gzip', 1, 2, $metadata); - // The last chunk completes the upload. $this->assertSame(2, $device->upload(new Stream('second'), '/root/archive.tar.gz', 'application/gzip', 2, 2, $metadata)); $this->assertCount(6, $client->requests); - $this->assertSame('second', (string) $client->requests[3]->getBody(), 'the retried part carries its whole body again'); + $this->assertSame('second', (string) $client->requests[3]->getBody()); $this->assertStringContainsString('"etag-2"2', (string) $client->requests[5]->getBody()); } @@ -452,7 +450,7 @@ public function testTimedOutMultipartCreationIsNotReplayed(): void } catch (TransportException) { } - $this->assertCount(1, $client->requests, 'a second POST could open a second multipart upload'); + $this->assertCount(1, $client->requests); $this->assertArrayNotHasKey('uploadId', $metadata); } From c1ffd8b49c6a848589b5113526641a0cf855500f Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 16:16:33 +0530 Subject: [PATCH 462/470] fix(storage): only drop an upload's record once its file is gone Keep the pending document when the joined file could not be removed after an unclean scan, report a failed document delete, and have Local::abort() succeed only when the joined file no longer exists. --- packages/storage/src/Device/Local.php | 2 +- .../Modules/Storage/Http/Buckets/Files/Create.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/storage/src/Device/Local.php b/packages/storage/src/Device/Local.php index 4525b9d9f89..68f22f95483 100644 --- a/packages/storage/src/Device/Local.php +++ b/packages/storage/src/Device/Local.php @@ -224,7 +224,7 @@ public function abort(string $path, string $uploadId = ''): bool // Chunks that were already joined into the file leave no directory behind. if (! file_exists($tmp)) { - return true; + return ! file_exists($path); } $files = $this->scanDirectory($tmp); diff --git a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php index 064f521a07c..43f457db5fc 100644 --- a/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php +++ b/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php @@ -378,12 +378,12 @@ public function action( } if (!$scan->isClean()) { - // The finalized upload has no completed file record yet, and a pending - // one would be left pointing at chunks that are already joined. - $deviceForFiles->delete($path); + // A pending record would be left pointing at chunks that are already joined. Keep it + // while the joined file is still on disk, so the removal can be retried by deleting the file. + $removed = $deviceForFiles->delete($path); - if (!$file->isEmpty()) { - $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId)); + if ($removed && !$file->isEmpty() && !$authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId))) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove file from DB'); } } From 729c2b498bc81463a5c6c8d70550d6f37a3052aa Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 11:48:55 +0100 Subject: [PATCH 463/470] fix(storage): only replay S3 requests that cannot apply twice A CreateMultipartUpload or conditional PUT answered with InternalError, or dropped mid-flight, may already have been applied: replaying the first can orphan an upload, the second reports a false precondition failure. Retry those only when the request was rejected (throttled or never sent). Also recognise Swoole's refused connection. --- .../storage/src/Device/S3/RetryStrategy.php | 87 ++++++++++++------- .../tests/Device/S3/RetryStrategyTest.php | 58 ++++++++++--- packages/storage/tests/Device/S3Test.php | 16 +++- 3 files changed, 118 insertions(+), 43 deletions(-) diff --git a/packages/storage/src/Device/S3/RetryStrategy.php b/packages/storage/src/Device/S3/RetryStrategy.php index d885d1cfe98..388760aa0cb 100644 --- a/packages/storage/src/Device/S3/RetryStrategy.php +++ b/packages/storage/src/Device/S3/RetryStrategy.php @@ -20,10 +20,13 @@ * Retry strategy for transient S3 failures, for use with the * `utopia-php/client` Retry decorator. * - * The XML error code wins over the HTTP status; unparseable 429/5xx responses - * fall back to the status. Transport failures are retried only when replaying - * cannot apply the request twice. Waits use exponential backoff with full - * jitter, or a numeric Retry-After. + * A rejected request (throttled, or never sent) is always retried. One that may + * have been applied (internal error, dropped or timed-out connection) is only + * retried when replaying it is harmless: not a CreateMultipartUpload, which + * would open a second upload, and not a conditional write, whose condition the + * first attempt may already have changed. The XML error code wins over the HTTP + * status. Waits use exponential backoff with full jitter, or a numeric + * Retry-After. * @see \Utopia\Storage\Tests\Device\S3\RetryStrategyTest */ final readonly class RetryStrategy implements Strategy @@ -31,12 +34,26 @@ /** * @var array */ - private const array TRANSIENT_ERROR_CODES = ['SlowDown', 'ServiceUnavailable', 'Throttling', 'RequestThrottled', 'InternalError']; + private const array REJECTED_ERROR_CODES = ['SlowDown', 'ServiceUnavailable', 'Throttling', 'RequestThrottled']; + + /** + * @var array + */ + private const array UNKNOWN_ERROR_CODES = ['InternalError']; /** * @var array */ - private const array TRANSIENT_STATUS_CODES = [429, 500, 502, 503, 504]; + private const array REJECTED_STATUS_CODES = [429, 503]; + + /** + * @var array + */ + private const array UNKNOWN_STATUS_CODES = [500, 502, 504]; + + private const string REJECTED = 'rejected'; + + private const string UNKNOWN = 'unknown'; private Closure $randomizer; @@ -61,53 +78,65 @@ public function delay(RequestInterface $request, int $attempt, ?ResponseInterfac return null; } - $transient = $response instanceof ResponseInterface - ? $this->isTransient($response) - : $this->isReplayable($request, $error); + $outcome = $response instanceof ResponseInterface + ? $this->classifyResponse($response) + : $this->classifyError($error); - if (! $transient) { + if ($outcome === null || ($outcome === self::UNKNOWN && ! $this->isReplayable($request))) { return null; } return $this->retryAfter($response) ?? ($this->randomizer)() * min($this->maxDelay, $this->delay * 2 ** ($attempt - 1)); } - private function isTransient(ResponseInterface $response): bool + private function classifyResponse(ResponseInterface $response): ?string { $body = (string) $response->getBody(); $trimmed = ltrim($body); if (str_starts_with($trimmed, 'Code ?? ''); - if (\in_array($code, self::TRANSIENT_ERROR_CODES, true)) { - return true; - } - // Successfully parsed XML with a non-transient error code — do not retry. - if ($code !== '') { - return false; - } + $code = $xml === false ? '' : (string) ($xml->Code ?? ''); + if ($code !== '') { + return match (true) { + \in_array($code, self::REJECTED_ERROR_CODES, true) => self::REJECTED, + \in_array($code, self::UNKNOWN_ERROR_CODES, true) => self::UNKNOWN, + default => null, + }; } } - // Fall back to HTTP status code for responses that cannot be parsed as XML. - return \in_array($response->getStatusCode(), self::TRANSIENT_STATUS_CODES, true); + return match (true) { + \in_array($response->getStatusCode(), self::REJECTED_STATUS_CODES, true) => self::REJECTED, + \in_array($response->getStatusCode(), self::UNKNOWN_STATUS_CODES, true) => self::UNKNOWN, + default => null, + }; } - private function isReplayable(RequestInterface $request, ?ClientExceptionInterface $error): bool + private function classifyError(?ClientExceptionInterface $error): ?string { - // Nothing was sent. - if ($error instanceof DnsException || ($error instanceof ConnectionException && $error->getCode() === \CURLE_COULDNT_CONNECT)) { - return true; + // Nothing was sent: cURL and Swoole report a refused connection differently. + $refused = [\CURLE_COULDNT_CONNECT, \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111]; + if ($error instanceof DnsException || ($error instanceof ConnectionException && \in_array($error->getCode(), $refused, true))) { + return self::REJECTED; } if ($error instanceof TlsException) { - return false; + return null; + } + + return $error instanceof ConnectionException || $error instanceof TimeoutException ? self::UNKNOWN : null; + } + + private function isReplayable(RequestInterface $request): bool + { + if ($request->getMethod() === Method::PUT) { + return ! $request->hasHeader(Header::IF_MATCH) && ! $request->hasHeader(Header::IF_NONE_MATCH); } - // A replayed POST may open a second multipart upload. - return ($error instanceof ConnectionException || $error instanceof TimeoutException) && $request->getMethod() !== Method::POST; + parse_str($request->getUri()->getQuery(), $query); + + return $request->getMethod() !== Method::POST || !\array_key_exists('uploads', $query); } private function retryAfter(?ResponseInterface $response): ?float diff --git a/packages/storage/tests/Device/S3/RetryStrategyTest.php b/packages/storage/tests/Device/S3/RetryStrategyTest.php index fb272ef2faf..6d6c9b121be 100644 --- a/packages/storage/tests/Device/S3/RetryStrategyTest.php +++ b/packages/storage/tests/Device/S3/RetryStrategyTest.php @@ -17,9 +17,14 @@ final class RetryStrategyTest extends TestCase { - private function request(string $method = 'PUT'): Request + private function request(string $method = 'PUT', string $query = ''): Request { - return new Request($method, Uri::parse('https://s3.example.com/root/file.txt')); + return new Request($method, Uri::parse('https://s3.example.com/root/file.txt' . ($query === '' ? '' : '?' . $query))); + } + + private function internalError(): Response + { + return $this->response(500, 'InternalErrorinternal incident'); } private function response(int $status, string $body = ''): Response @@ -68,9 +73,36 @@ public function testStatusFallbackIsTransient(): void public function testInternalErrorIsRetried(): void { - $body = 'InternalErrorinternal incident'; + $strategy = new RetryStrategy(); + + $this->assertNotNull($strategy->delay($this->request('PUT', 'partNumber=2&uploadId=upload-1'), 1, $this->internalError(), null)); + $this->assertNotNull($strategy->delay($this->request('POST', 'uploadId=upload-1'), 1, $this->internalError(), null), 'completion'); + } + + public function testMaybeAppliedMultipartCreationIsNotRetried(): void + { + $strategy = new RetryStrategy(); + $create = $this->request('POST', 'uploads='); + + $this->assertNull($strategy->delay($create, 1, $this->internalError(), null)); + $this->assertNull($strategy->delay($create, 1, null, new TimeoutException($create, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); + $this->assertNotNull($strategy->delay($create, 1, $this->response(503), null), 'throttled, so never applied'); + } + + public function testMaybeAppliedConditionalWriteIsNotRetried(): void + { + $strategy = new RetryStrategy(); - $this->assertNotNull(new RetryStrategy()->delay($this->request('POST'), 1, $this->response(500, $body), null)); + foreach (['If-None-Match' => '*', 'If-Match' => '"etag"'] as $header => $value) { + $write = $this->request('PUT')->withHeader($header, $value); + + $this->assertNull($strategy->delay($write, 1, $this->internalError(), null), $header); + $this->assertNull($strategy->delay($write, 1, null, new ConnectionException($write, 'Connection reset by peer', \CURLE_RECV_ERROR)), $header); + $this->assertNotNull($strategy->delay($write, 1, $this->response(503), null), $header . ' throttled, so never applied'); + } + + $read = $this->request('GET')->withHeader('If-Match', '"etag"'); + $this->assertNotNull($strategy->delay($read, 1, $this->internalError(), null), 'a conditional read is harmless to repeat'); } /** XML error code takes precedence over HTTP status — 503 with non-transient XML must not be retried. */ @@ -100,13 +132,14 @@ public function testRetriesAreCapped(): void $this->assertNull($strategy->delay($this->request(), 3, $response, null)); } - public function testFailuresBeforeSendingAreRetriedForEveryMethod(): void + public function testFailuresBeforeSendingAreRetriedForEveryRequest(): void { $strategy = new RetryStrategy(); - $post = $this->request('POST'); + $create = $this->request('POST', 'uploads='); - $this->assertNotNull($strategy->delay($post, 1, null, new ConnectionException($post, 'Could not connect to server', \CURLE_COULDNT_CONNECT))); - $this->assertNotNull($strategy->delay($post, 1, null, new DnsException($post, 'Could not resolve host', \CURLE_COULDNT_RESOLVE_HOST))); + $this->assertNotNull($strategy->delay($create, 1, null, new ConnectionException($create, 'Could not connect to server', \CURLE_COULDNT_CONNECT)), 'cURL'); + $this->assertNotNull($strategy->delay($create, 1, null, new ConnectionException($create, 'Connection refused', \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111)), 'Swoole'); + $this->assertNotNull($strategy->delay($create, 1, null, new DnsException($create, 'Could not resolve host', \CURLE_COULDNT_RESOLVE_HOST))); } public function testDroppedOrTimedOutPutIsRetried(): void @@ -118,13 +151,12 @@ public function testDroppedOrTimedOutPutIsRetried(): void $this->assertNotNull($strategy->delay($put, 1, null, new TimeoutException($put, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); } - public function testDroppedOrTimedOutPostIsNotRetried(): void + public function testTimedOutCompletionIsRetried(): void { - $strategy = new RetryStrategy(); - $post = $this->request('POST'); + // A completion that did land leaves the object in place, which finalize() accepts. + $complete = $this->request('POST', 'uploadId=upload-1'); - $this->assertNull($strategy->delay($post, 1, null, new ConnectionException($post, 'Connection reset by peer', \CURLE_RECV_ERROR))); - $this->assertNull($strategy->delay($post, 1, null, new TimeoutException($post, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); + $this->assertNotNull(new RetryStrategy()->delay($complete, 1, null, new TimeoutException($complete, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); } public function testTlsFailureIsNotRetried(): void diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index 24e273b0e80..1577b7b8d27 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -422,7 +422,21 @@ public function testMultipartUploadSurvivesInternalErrors(): void $this->assertSame(2, $device->upload(new Stream('second'), '/root/archive.tar.gz', 'application/gzip', 2, 2, $metadata)); $this->assertCount(6, $client->requests); $this->assertSame('second', (string) $client->requests[3]->getBody()); - $this->assertStringContainsString('"etag-2"2', (string) $client->requests[5]->getBody()); + } + + public function testConditionalWriteIsNotReplayedAfterAnInternalError(): void + { + // The first attempt may have written the object, so a replay would fail its own If-None-Match. + $client = new ScriptedClient([$this->internalIncident()]); + + try { + $this->device($client)->create('/root/file.txt', new Stream('Hello World'), 'text/plain'); + self::fail('Expected the internal error to surface'); + } catch (RemoteException $e) { + $this->assertSame('InternalError', $e->errorCode); + } + + $this->assertCount(1, $client->requests); } public function testTimedOutPartIsReplayed(): void From c635dd2ebf840c1de1532d7c658bd749a3b2f6d3 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 11:53:08 +0100 Subject: [PATCH 464/470] fix(storage): accept a replayed completion in large server-side copies Completions are now retried after a timeout, so a copy whose first completion landed can get NoSuchUpload on the replay. Treat it as success when the target exists, as finalize() already does, instead of aborting and reporting failure. --- packages/storage/src/Device/S3.php | 12 +++++++++- packages/storage/tests/Device/S3Test.php | 28 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/storage/src/Device/S3.php b/packages/storage/src/Device/S3.php index 865c1baea46..3fc8209d8c3 100644 --- a/packages/storage/src/Device/S3.php +++ b/packages/storage/src/Device/S3.php @@ -514,7 +514,17 @@ private function copyObject(string $source, string $target): bool } $parts[$part] = $etag; } - $this->completeMultipartUpload($target, $uploadId, $parts); + + try { + $this->completeMultipartUpload($target, $uploadId, $parts); + } catch (NotFoundException $e) { + // A replayed completion finds the upload gone once the first one landed. + if ($this->exists($target)) { + return true; + } + + throw $e; + } } catch (\Throwable $e) { // Best effort — unclaimed multipart parts are billed until aborted. try { diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index 1577b7b8d27..15dbc302495 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -419,6 +419,7 @@ public function testMultipartUploadSurvivesInternalErrors(): void $device->prepare('/root/archive.tar.gz', 'application/gzip', 2, $metadata); $device->upload(new Stream('first'), '/root/archive.tar.gz', 'application/gzip', 1, 2, $metadata); + // The last chunk completes the upload. $this->assertSame(2, $device->upload(new Stream('second'), '/root/archive.tar.gz', 'application/gzip', 2, 2, $metadata)); $this->assertCount(6, $client->requests); $this->assertSame('second', (string) $client->requests[3]->getBody()); @@ -560,6 +561,33 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertStringContainsString('etag-2', $s3->completedBody); } + public function testLargeCopySurvivesAReplayedCompletion(): void + { + $size = (string) (6 * 1024 * 1024 * 1024); + $xml = static fn (string $body): Response => new Response(200, body: new Stream('' . $body))->withHeader('content-type', 'application/xml'); + $client = new ScriptedClient([ + new Response(200)->withHeader('content-length', $size), + $xml('upload-1'), + $xml('"etag-1"'), + $xml('"etag-2"'), + new TimeoutException(new Request('POST', Uri::parse('https://s3.example.com/root/b.bin')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), + new Response(404, body: new Stream('NoSuchUploadThe specified upload does not exist.')), + new Response(200)->withHeader('content-length', $size), + ]); + $device = new S3( + root: '/root', + accessKey: 'test-key', + secretKey: 'test-secret', + host: 'https://s3.example.com', + region: 'us-east-1', + client: new Retry($client, new RetryStrategy(delay: 0.0)), + bucket: 'my-bucket', + ); + + $this->assertTrue($device->copy('/root/a.bin', '/root/b.bin')); + $this->assertNotContains('DELETE', array_map(static fn (RequestInterface $request): string => $request->getMethod(), $client->requests), 'the landed copy is not aborted'); + } + public function testCopyWithoutBucketFallsBackToStreaming(): void { $this->s3->objectExists = true; From a1aff98ea8b24a7ed1ee094937c542f2e43c89e9 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 12:07:34 +0100 Subject: [PATCH 465/470] fix(storage): prove a recovered copy is ours and never replay batch deletes An existing target made NoSuchUpload look like a landed copy, so move() could delete its source for a copy that never completed; accept it only when the target's ETag is the multipart ETag of these parts. Batch deletes (POST ?delete) were replayable after an ambiguous outcome and could remove an object written in between; CompleteMultipartUpload is now the only POST replayed. --- packages/storage/src/Device/S3.php | 12 +++- .../storage/src/Device/S3/RetryStrategy.php | 11 +++- .../tests/Device/S3/RetryStrategyTest.php | 11 ++++ packages/storage/tests/Device/S3Test.php | 58 +++++++++++++++---- 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/packages/storage/src/Device/S3.php b/packages/storage/src/Device/S3.php index 3fc8209d8c3..c8918c39ff4 100644 --- a/packages/storage/src/Device/S3.php +++ b/packages/storage/src/Device/S3.php @@ -518,8 +518,16 @@ private function copyObject(string $source, string $target): bool try { $this->completeMultipartUpload($target, $uploadId, $parts); } catch (NotFoundException $e) { - // A replayed completion finds the upload gone once the first one landed. - if ($this->exists($target)) { + // A replayed completion finds the upload gone once the first one landed; + // only an ETag built from these parts proves the target is this copy. + $digests = implode('', array_map(fn (string $etag): string => (string) hex2bin($this->unquote($etag)), $parts)); + try { + $landed = $this->unquote($this->getInfo($target)['etag'] ?? '') === md5($digests) . '-' . \count($parts); + } catch (NotFoundException) { + $landed = false; + } + + if ($landed) { return true; } diff --git a/packages/storage/src/Device/S3/RetryStrategy.php b/packages/storage/src/Device/S3/RetryStrategy.php index 388760aa0cb..848141fb967 100644 --- a/packages/storage/src/Device/S3/RetryStrategy.php +++ b/packages/storage/src/Device/S3/RetryStrategy.php @@ -22,8 +22,8 @@ * * A rejected request (throttled, or never sent) is always retried. One that may * have been applied (internal error, dropped or timed-out connection) is only - * retried when replaying it is harmless: not a CreateMultipartUpload, which - * would open a second upload, and not a conditional write, whose condition the + * retried when replaying it is harmless: not a POST other than + * CompleteMultipartUpload, and not a conditional write, whose condition the * first attempt may already have changed. The XML error code wins over the HTTP * status. Waits use exponential backoff with full jitter, or a numeric * Retry-After. @@ -134,9 +134,14 @@ private function isReplayable(RequestInterface $request): bool return ! $request->hasHeader(Header::IF_MATCH) && ! $request->hasHeader(Header::IF_NONE_MATCH); } + if ($request->getMethod() !== Method::POST) { + return true; + } + + // Of the POSTs, only CompleteMultipartUpload is safe: a landed completion leaves the object in place. parse_str($request->getUri()->getQuery(), $query); - return $request->getMethod() !== Method::POST || !\array_key_exists('uploads', $query); + return \array_key_exists('uploadId', $query); } private function retryAfter(?ResponseInterface $response): ?float diff --git a/packages/storage/tests/Device/S3/RetryStrategyTest.php b/packages/storage/tests/Device/S3/RetryStrategyTest.php index 6d6c9b121be..46fdb92ec1e 100644 --- a/packages/storage/tests/Device/S3/RetryStrategyTest.php +++ b/packages/storage/tests/Device/S3/RetryStrategyTest.php @@ -89,6 +89,17 @@ public function testMaybeAppliedMultipartCreationIsNotRetried(): void $this->assertNotNull($strategy->delay($create, 1, $this->response(503), null), 'throttled, so never applied'); } + public function testMaybeAppliedBatchDeleteIsNotRetried(): void + { + // A replay could delete an object written under a listed key since the first attempt. + $strategy = new RetryStrategy(); + $delete = $this->request('POST', 'delete='); + + $this->assertNull($strategy->delay($delete, 1, $this->internalError(), null)); + $this->assertNull($strategy->delay($delete, 1, null, new TimeoutException($delete, 'Operation timed out', \CURLE_OPERATION_TIMEDOUT))); + $this->assertNotNull($strategy->delay($delete, 1, $this->response(503), null), 'throttled, so never applied'); + } + public function testMaybeAppliedConditionalWriteIsNotRetried(): void { $strategy = new RetryStrategy(); diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index 15dbc302495..cca4b638486 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -561,20 +561,27 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertStringContainsString('etag-2', $s3->completedBody); } - public function testLargeCopySurvivesAReplayedCompletion(): void + /** + * @param array $afterCompletion + */ + private function largeCopy(array $afterCompletion): ScriptedClient { - $size = (string) (6 * 1024 * 1024 * 1024); $xml = static fn (string $body): Response => new Response(200, body: new Stream('' . $body))->withHeader('content-type', 'application/xml'); - $client = new ScriptedClient([ - new Response(200)->withHeader('content-length', $size), + + return new ScriptedClient([ + new Response(200)->withHeader('content-length', (string) (6 * 1024 * 1024 * 1024)), $xml('upload-1'), - $xml('"etag-1"'), - $xml('"etag-2"'), + $xml('"' . md5('part-1') . '"'), + $xml('"' . md5('part-2') . '"'), new TimeoutException(new Request('POST', Uri::parse('https://s3.example.com/root/b.bin')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), new Response(404, body: new Stream('NoSuchUploadThe specified upload does not exist.')), - new Response(200)->withHeader('content-length', $size), + ...$afterCompletion, ]); - $device = new S3( + } + + private function bucketDeviceFor(ScriptedClient $client): S3 + { + return new S3( root: '/root', accessKey: 'test-key', secretKey: 'test-secret', @@ -583,9 +590,40 @@ public function testLargeCopySurvivesAReplayedCompletion(): void client: new Retry($client, new RetryStrategy(delay: 0.0)), bucket: 'my-bucket', ); + } + + /** + * @return array + */ + private function methods(ScriptedClient $client): array + { + return array_map(static fn (RequestInterface $request): string => $request->getMethod(), $client->requests); + } + + public function testLargeCopySurvivesAReplayedCompletion(): void + { + // S3 names a multipart object after the MD5 of its parts' MD5s and the part count. + $landed = '"' . md5(hex2bin(md5('part-1')) . hex2bin(md5('part-2'))) . '-2"'; + $client = $this->largeCopy([new Response(200)->withHeader('etag', $landed)]); + + $this->assertTrue($this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin')); + $this->assertNotContains('DELETE', $this->methods($client), 'the landed copy is not aborted'); + } + + public function testLargeCopyOverAnotherObjectIsNotMistakenForItsOwn(): void + { + $client = $this->largeCopy([ + new Response(200)->withHeader('etag', '"' . md5('an older object') . '"'), + new Response(204), + ]); + + try { + $this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin'); + self::fail('Expected the copy to fail'); + } catch (NotFoundException) { + } - $this->assertTrue($device->copy('/root/a.bin', '/root/b.bin')); - $this->assertNotContains('DELETE', array_map(static fn (RequestInterface $request): string => $request->getMethod(), $client->requests), 'the landed copy is not aborted'); + $this->assertSame('DELETE', $this->methods($client)[7], 'the unfinished upload is aborted'); } public function testCopyWithoutBucketFallsBackToStreaming(): void From a92e52a80a0ae32d7423752e00e33f864ea53d4a Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 29 Sep 2026 16:51:07 +0530 Subject: [PATCH 466/470] feat(messaging): include messageId in the Appwrite push payload The MQTT payload delivered to clients carried no campaign id, so a client could not dedup a message received over another channel (e.g. APNs in the background). Add messageId to buildPayload()'s envelope; it was already the ledger's dedup key. --- src/Appwrite/Messaging/Adapter/Push/Appwrite.php | 4 ++++ tests/unit/Appwrite/Messaging/Adapter/Push/AppwriteTest.php | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Appwrite/Messaging/Adapter/Push/Appwrite.php b/src/Appwrite/Messaging/Adapter/Push/Appwrite.php index 00730b59424..036cbcb14f0 100644 --- a/src/Appwrite/Messaging/Adapter/Push/Appwrite.php +++ b/src/Appwrite/Messaging/Adapter/Push/Appwrite.php @@ -238,6 +238,10 @@ private function buildPayload(PushMessage $message): string { $envelope = []; + if ($this->messageId !== '') { + $envelope['messageId'] = $this->messageId; + } + if ($message->getTitle() !== null) { $envelope['notification']['title'] = $message->getTitle(); } diff --git a/tests/unit/Appwrite/Messaging/Adapter/Push/AppwriteTest.php b/tests/unit/Appwrite/Messaging/Adapter/Push/AppwriteTest.php index 493a1bc9b48..44a9c88147b 100644 --- a/tests/unit/Appwrite/Messaging/Adapter/Push/AppwriteTest.php +++ b/tests/unit/Appwrite/Messaging/Adapter/Push/AppwriteTest.php @@ -228,6 +228,7 @@ public function testPayloadEnvelopeShape(): void $this->assertSame('India vs Australia', $payload['notification']['body']); $this->assertSame(['matchId' => '42'], $payload['data']); $this->assertSame('high', $payload['priority']); + $this->assertSame('msg-1', $payload['messageId']); } public function testSequenceAdvancesPerPublish(): void From fc66e8226d121b374043c10fa65e66666ad88cf1 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 12:23:51 +0100 Subject: [PATCH 467/470] fix(storage): recognise a landed copy by the target changing, not by its ETag format The multipart ETag formula is AWS's and other S3-compatible providers may not follow it, which turned a landed copy back into a reported failure. Record the target's ETag before the copy and accept NoSuchUpload on the completion only when the target has since changed to an object of the source's size. --- packages/storage/src/Device/S3.php | 17 ++++++++++------ packages/storage/tests/Device/S3Test.php | 26 +++++++++++++----------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/storage/src/Device/S3.php b/packages/storage/src/Device/S3.php index c8918c39ff4..9b7ce1dd606 100644 --- a/packages/storage/src/Device/S3.php +++ b/packages/storage/src/Device/S3.php @@ -491,6 +491,12 @@ private function copyObject(string $source, string $target): bool return true; } + try { + $previous = $this->unquote($this->getInfo($target)['etag'] ?? ''); + } catch (NotFoundException) { + $previous = null; + } + $uploadId = $this->createMultipartUpload($target, $info['content-type'] ?? ''); try { $parts = []; @@ -518,16 +524,15 @@ private function copyObject(string $source, string $target): bool try { $this->completeMultipartUpload($target, $uploadId, $parts); } catch (NotFoundException $e) { - // A replayed completion finds the upload gone once the first one landed; - // only an ETag built from these parts proves the target is this copy. - $digests = implode('', array_map(fn (string $etag): string => (string) hex2bin($this->unquote($etag)), $parts)); + // A replayed completion finds the upload gone once the first one landed, + // which shows as a target that changed since the copy began. try { - $landed = $this->unquote($this->getInfo($target)['etag'] ?? '') === md5($digests) . '-' . \count($parts); + $current = $this->getInfo($target); } catch (NotFoundException) { - $landed = false; + throw $e; } - if ($landed) { + if ($this->unquote($current['etag'] ?? '') !== $previous && (int) ($current['content-length'] ?? -1) === $size) { return true; } diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index cca4b638486..8ec4cd61808 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -549,6 +549,7 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertTrue($s3->copy('/root/a.bin', '/root/b.bin')); $this->assertSame([ + 's3:info', 's3:info', 's3:createMultipartUpload', 's3:uploadPartCopy', @@ -561,18 +562,21 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertStringContainsString('etag-2', $s3->completedBody); } + private const string LARGE = '6442450944'; + /** * @param array $afterCompletion */ - private function largeCopy(array $afterCompletion): ScriptedClient + private function largeCopy(ResponseInterface $target, array $afterCompletion): ScriptedClient { $xml = static fn (string $body): Response => new Response(200, body: new Stream('' . $body))->withHeader('content-type', 'application/xml'); return new ScriptedClient([ - new Response(200)->withHeader('content-length', (string) (6 * 1024 * 1024 * 1024)), + new Response(200)->withHeader('content-length', self::LARGE), + $target, $xml('upload-1'), - $xml('"' . md5('part-1') . '"'), - $xml('"' . md5('part-2') . '"'), + $xml('"etag-1"'), + $xml('"etag-2"'), new TimeoutException(new Request('POST', Uri::parse('https://s3.example.com/root/b.bin')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), new Response(404, body: new Stream('NoSuchUploadThe specified upload does not exist.')), ...$afterCompletion, @@ -602,9 +606,9 @@ private function methods(ScriptedClient $client): array public function testLargeCopySurvivesAReplayedCompletion(): void { - // S3 names a multipart object after the MD5 of its parts' MD5s and the part count. - $landed = '"' . md5(hex2bin(md5('part-1')) . hex2bin(md5('part-2'))) . '-2"'; - $client = $this->largeCopy([new Response(200)->withHeader('etag', $landed)]); + $client = $this->largeCopy(new Response(404), [ + new Response(200)->withHeader('etag', '"etag-copy-2"')->withHeader('content-length', self::LARGE), + ]); $this->assertTrue($this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin')); $this->assertNotContains('DELETE', $this->methods($client), 'the landed copy is not aborted'); @@ -612,10 +616,8 @@ public function testLargeCopySurvivesAReplayedCompletion(): void public function testLargeCopyOverAnotherObjectIsNotMistakenForItsOwn(): void { - $client = $this->largeCopy([ - new Response(200)->withHeader('etag', '"' . md5('an older object') . '"'), - new Response(204), - ]); + $existing = new Response(200)->withHeader('etag', '"etag-older"')->withHeader('content-length', self::LARGE); + $client = $this->largeCopy($existing, [$existing, new Response(204)]); try { $this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin'); @@ -623,7 +625,7 @@ public function testLargeCopyOverAnotherObjectIsNotMistakenForItsOwn(): void } catch (NotFoundException) { } - $this->assertSame('DELETE', $this->methods($client)[7], 'the unfinished upload is aborted'); + $this->assertContains('DELETE', $this->methods($client), 'the unfinished upload is aborted'); } public function testCopyWithoutBucketFallsBackToStreaming(): void From 4466fc14e8610e2a6f95f471b88e53bf8b4c4b34 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 29 Sep 2026 16:57:00 +0530 Subject: [PATCH 468/470] test(mqtt): assert the live subscriber receives the campaign messageId The unit test only checked the payload recorded by a fake broker. Assert messageId in the payload delivered to the live MQTT subscriber too, so a regression dropping the campaign id from the wire is caught. --- tests/e2e/Services/Mqtt/MqttServerTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Mqtt/MqttServerTest.php b/tests/e2e/Services/Mqtt/MqttServerTest.php index 4dbfe7cfbfa..ff48fa68d5e 100644 --- a/tests/e2e/Services/Mqtt/MqttServerTest.php +++ b/tests/e2e/Services/Mqtt/MqttServerTest.php @@ -416,7 +416,7 @@ private function setupPushTopic(array $server, string $userId, string $name, int * @param array $server * @param array $data */ - private function publishCampaign(array $server, string $topicId, string $title, string $body, array $data = [], array $extra = []): void + private function publishCampaign(array $server, string $topicId, string $title, string $body, array $data = [], array $extra = []): string { $push = $this->client->call(Client::METHOD_POST, '/messaging/messages/push', $server, \array_merge([ 'messageId' => ID::unique(), @@ -432,6 +432,8 @@ private function publishCampaign(array $server, string $topicId, string $title, $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, $server); $this->assertContains($message['body']['status'], [MessageStatus::SENT, MessageStatus::FAILED]); }, 30000, 500); + + return $messageId; } /** @@ -501,7 +503,7 @@ public function testCampaignFansOutToMqttSubscriber(): void $subscriber->subscribe([$topicName]); try { - $this->publishCampaign($server, $topicId, 'Match update', 'India needs 12 off 6', ['matchId' => '42']); + $messageId = $this->publishCampaign($server, $topicId, 'Match update', 'India needs 12 off 6', ['matchId' => '42']); $received = $subscriber->consume(limit: 1, timeout: 20.0); } finally { $subscriber->disconnect(); @@ -516,6 +518,7 @@ public function testCampaignFansOutToMqttSubscriber(): void $this->assertEquals('Match update', $payload['notification']['title']); $this->assertEquals('India needs 12 off 6', $payload['notification']['body']); $this->assertEquals(['matchId' => '42'], $payload['data']); + $this->assertSame($messageId, $payload['messageId']); } public function testCampaignCarriesChannelIdToSubscriber(): void From 46a9fb73007a515f413726bdae50e081fa27d487 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 29 Sep 2026 12:36:51 +0100 Subject: [PATCH 469/470] fix(storage): report an unproven multipart copy as failed Nothing distinguishes a copy whose replayed completion finds the upload gone from one that was aborted or overwritten; every inference tried could report a false success, letting move() delete a source that was never copied. Keep copyObject() as on main: repeating a copy is safe, a false success is not. --- packages/storage/src/Device/S3.php | 25 +---------- packages/storage/tests/Device/S3Test.php | 56 +++++------------------- 2 files changed, 11 insertions(+), 70 deletions(-) diff --git a/packages/storage/src/Device/S3.php b/packages/storage/src/Device/S3.php index 9b7ce1dd606..865c1baea46 100644 --- a/packages/storage/src/Device/S3.php +++ b/packages/storage/src/Device/S3.php @@ -491,12 +491,6 @@ private function copyObject(string $source, string $target): bool return true; } - try { - $previous = $this->unquote($this->getInfo($target)['etag'] ?? ''); - } catch (NotFoundException) { - $previous = null; - } - $uploadId = $this->createMultipartUpload($target, $info['content-type'] ?? ''); try { $parts = []; @@ -520,24 +514,7 @@ private function copyObject(string $source, string $target): bool } $parts[$part] = $etag; } - - try { - $this->completeMultipartUpload($target, $uploadId, $parts); - } catch (NotFoundException $e) { - // A replayed completion finds the upload gone once the first one landed, - // which shows as a target that changed since the copy began. - try { - $current = $this->getInfo($target); - } catch (NotFoundException) { - throw $e; - } - - if ($this->unquote($current['etag'] ?? '') !== $previous && (int) ($current['content-length'] ?? -1) === $size) { - return true; - } - - throw $e; - } + $this->completeMultipartUpload($target, $uploadId, $parts); } catch (\Throwable $e) { // Best effort — unclaimed multipart parts are billed until aborted. try { diff --git a/packages/storage/tests/Device/S3Test.php b/packages/storage/tests/Device/S3Test.php index 8ec4cd61808..c429e2108d4 100644 --- a/packages/storage/tests/Device/S3Test.php +++ b/packages/storage/tests/Device/S3Test.php @@ -549,7 +549,6 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertTrue($s3->copy('/root/a.bin', '/root/b.bin')); $this->assertSame([ - 's3:info', 's3:info', 's3:createMultipartUpload', 's3:uploadPartCopy', @@ -562,30 +561,24 @@ public function testCopyLargeObjectUsesMultipartServerSideCopy(): void $this->assertStringContainsString('etag-2', $s3->completedBody); } - private const string LARGE = '6442450944'; - /** - * @param array $afterCompletion + * Nothing tells a copy whose replayed completion finds the upload gone apart from + * one that was aborted, so it is reported as failed: repeating a copy is safe, + * while a false success would let move() delete its source. */ - private function largeCopy(ResponseInterface $target, array $afterCompletion): ScriptedClient + public function testLargeCopyWithAnUnprovenCompletionFails(): void { $xml = static fn (string $body): Response => new Response(200, body: new Stream('' . $body))->withHeader('content-type', 'application/xml'); - - return new ScriptedClient([ - new Response(200)->withHeader('content-length', self::LARGE), - $target, + $client = new ScriptedClient([ + new Response(200)->withHeader('content-length', '6442450944'), $xml('upload-1'), $xml('"etag-1"'), $xml('"etag-2"'), new TimeoutException(new Request('POST', Uri::parse('https://s3.example.com/root/b.bin')), 'Operation timed out', \CURLE_OPERATION_TIMEDOUT), new Response(404, body: new Stream('NoSuchUploadThe specified upload does not exist.')), - ...$afterCompletion, + new Response(204), ]); - } - - private function bucketDeviceFor(ScriptedClient $client): S3 - { - return new S3( + $device = new S3( root: '/root', accessKey: 'test-key', secretKey: 'test-secret', @@ -594,38 +587,9 @@ private function bucketDeviceFor(ScriptedClient $client): S3 client: new Retry($client, new RetryStrategy(delay: 0.0)), bucket: 'my-bucket', ); - } - - /** - * @return array - */ - private function methods(ScriptedClient $client): array - { - return array_map(static fn (RequestInterface $request): string => $request->getMethod(), $client->requests); - } - - public function testLargeCopySurvivesAReplayedCompletion(): void - { - $client = $this->largeCopy(new Response(404), [ - new Response(200)->withHeader('etag', '"etag-copy-2"')->withHeader('content-length', self::LARGE), - ]); - - $this->assertTrue($this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin')); - $this->assertNotContains('DELETE', $this->methods($client), 'the landed copy is not aborted'); - } - public function testLargeCopyOverAnotherObjectIsNotMistakenForItsOwn(): void - { - $existing = new Response(200)->withHeader('etag', '"etag-older"')->withHeader('content-length', self::LARGE); - $client = $this->largeCopy($existing, [$existing, new Response(204)]); - - try { - $this->bucketDeviceFor($client)->copy('/root/a.bin', '/root/b.bin'); - self::fail('Expected the copy to fail'); - } catch (NotFoundException) { - } - - $this->assertContains('DELETE', $this->methods($client), 'the unfinished upload is aborted'); + $this->expectException(NotFoundException::class); + $device->copy('/root/a.bin', '/root/b.bin'); } public function testCopyWithoutBucketFallsBackToStreaming(): void From 4b24f75b502061966eb743362a1d01d18ff26b44 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Tue, 29 Sep 2026 17:12:52 +0530 Subject: [PATCH 470/470] test(messaging): drop allowlist-mirroring validator test --- .../Validator/Queries/MessagesTest.php | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php diff --git a/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php b/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php deleted file mode 100644 index 6bccbcbac06..00000000000 --- a/tests/unit/Utopia/Database/Validator/Queries/MessagesTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertEquals(true, $validator->isValid([Query::contains('users', ['someUserId'])]), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid([Query::contains('targets', ['someTargetId'])]), $validator->getDescription()); - - /** - * Test for Failure - */ - $this->assertEquals(false, $validator->isValid([Query::contains('topics', ['someTopicId'])]), $validator->getDescription()); - } -}