diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 0000000..153ed8a --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,21 @@ +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/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8fa5b55..880da7f 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.1 b/Dockerfile.php-8.1 index 1ae98fb..3210555 100644 --- a/Dockerfile.php-8.1 +++ b/Dockerfile.php-8.1 @@ -22,5 +22,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" ] diff --git a/Dockerfile.php-8.2 b/Dockerfile.php-8.2 index 0cf128d..8c2d4ba 100644 --- a/Dockerfile.php-8.2 +++ b/Dockerfile.php-8.2 @@ -22,5 +22,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" ] diff --git a/Dockerfile.php-8.3 b/Dockerfile.php-8.3 index 23bab2d..63f9798 100644 --- a/Dockerfile.php-8.3 +++ b/Dockerfile.php-8.3 @@ -22,5 +22,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" ] diff --git a/composer.json b/composer.json index 16b4222..8e932b5 100755 --- a/composer.json +++ b/composer.json @@ -14,17 +14,20 @@ "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=aggregate" }, "require": { "php": ">=8.0", "ext-pdo": "*", "ext-curl": "*", + "ext-redis": "*", "utopia-php/database": "0.50.*" }, "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 133c237..e2fb9e9 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": "c01bd34998a95089e8e5f980d54fe747", + "content-hash": "1e53afffe12ae95f750f420f449e4c19", "packages": [ { "name": "jean85/pretty-package-versions", @@ -216,16 +216,16 @@ }, { "name": "utopia-php/cache", - "version": "0.10.1", + "version": "0.10.2", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "87ee4fc91e50d4ddfef650aa999ea12be3a99583" + "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/87ee4fc91e50d4ddfef650aa999ea12be3a99583", - "reference": "87ee4fc91e50d4ddfef650aa999ea12be3a99583", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247", + "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247", "shasum": "" }, "require": { @@ -260,9 +260,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.10.1" + "source": "https://github.com/utopia-php/cache/tree/0.10.2" }, - "time": "2024-06-18T13:20:25+00:00" + "time": "2024-06-25T20:36:35+00:00" }, { "name": "utopia-php/database", @@ -427,6 +427,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", @@ -497,6 +573,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", @@ -799,6 +952,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.11.5", @@ -1280,90 +1633,242 @@ "time": "2024-04-05T04:35:58+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/", @@ -2243,54 +2748,1073 @@ "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.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "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.1.2" }, "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-06-28T10:03:55+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" + }, + "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-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.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/v7.1.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-06-28T10:03:55+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "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.1.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-05-31T14:57:53+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "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.1.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-05-31T14:57:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "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.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-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "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.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-intl-normalizer", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "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.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" + }, + "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-06-19T12:30:46+00:00" + }, + { + "name": "symfony/process", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "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.1.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-05-31T14:57:53+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" + }, + "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-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "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/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", + "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.1.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-06-28T09:27:18+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": [], @@ -2301,7 +3825,8 @@ "platform": { "php": ">=8.0", "ext-pdo": "*", - "ext-curl": "*" + "ext-curl": "*", + "ext-redis": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/docker-compose.yml b/docker-compose.yml index 579d3e4..3e2b3fa 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: . @@ -17,6 +23,7 @@ services: networks: - abuse depends_on: + - redis - mysql volumes: - ./phpunit.xml:/code/phpunit.xml diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 0000000..adc40d1 --- /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/phpunit.xml b/phpunit.xml index 10d408c..ec39a7e 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 0cf1d28..4af27dc 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/Database.php b/src/Abuse/Adapters/Database.php new file mode 100644 index 0000000..534ee49 --- /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/ReCaptcha.php b/src/Abuse/Adapters/ReCaptcha.php index 6f8a179..ae117ac 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 new file mode 100644 index 0000000..d9b0892 --- /dev/null +++ b/src/Abuse/Adapters/Redis.php @@ -0,0 +1,138 @@ +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|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 + $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; + } + + /** + * Delete all logs older than $datetime + * + * @param string $datetime + * @return bool + */ + public function cleanup(string $datetime): bool + { + $iterator = null; + while ($iterator !== 0) { + $keys = $this->redis->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; + } +} diff --git a/src/Abuse/Adapters/TimeLimit.php b/src/Abuse/Adapters/TimeLimit.php index 038f32c..8040dc3 100644 --- a/src/Abuse/Adapters/TimeLimit.php +++ b/src/Abuse/Adapters/TimeLimit.php @@ -4,35 +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 implements Adapter +abstract class TimeLimit extends Adapter { - public const COLLECTION = 'abuse'; - - /** - * @var Database - */ - protected Database $db; - - /** - * @var string - */ - protected string $key = ''; - - /** - * @var string - */ - protected string $time; - /** * @var int */ @@ -44,130 +18,9 @@ class TimeLimit implements Adapter protected ?int $count = null; /** - * @var array - */ - protected array $params = []; - - /** - * @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 - */ - 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 - } - } - - /** - * 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 + * @var string */ - protected function parseKey(): string - { - foreach ($this->getParams() as $key => $value) { - $this->key = \str_replace($key, $value, $this->key); - } - - return $this->key; - } + protected string $time; /** * Check @@ -180,147 +33,11 @@ protected function parseKey(): string * * @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(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(''); + abstract protected function count(string $key, string $datetime): int; - if (! \is_null($offset)) { - $queries[] = Query::offset($offset); - } - if (! \is_null($limit)) { - $queries[] = Query::limit($limit); - } + abstract protected function hit(string $key, string $datetime): void; - 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 diff --git a/tests/Abuse/AbuseTest.php b/tests/Abuse/Base.php old mode 100755 new mode 100644 similarity index 52% rename from tests/Abuse/AbuseTest.php rename to tests/Abuse/Base.php index 755b6e6..1b88f41 --- a/tests/Abuse/AbuseTest.php +++ b/tests/Abuse/Base.php @@ -2,54 +2,19 @@ namespace Utopia\Tests; -use PDO; use PHPUnit\Framework\TestCase; use Utopia\Abuse\Abuse; -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; -use Utopia\Exception; - -class AbuseTest extends TestCase +use Utopia\Abuse\Adapter; + +abstract class Base extends TestCase { protected Abuse $abuse; protected Abuse $abuseIp; - protected Database $db; + abstract public function getAdapter(string $key, int $limit, int $seconds): Adapter; - /** - * @throws Exception - * @throws \Exception - */ - public function setUp(): void - { - // 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')) { - $db->create(); - $adapter->setup(); - } - - $adapter->setParam('{{ip}}', '127.0.0.1'); - $this->abuse = new Abuse($adapter); - } + abstract public function getCleanupDateTime(): string; public function tearDown(): void { @@ -61,7 +26,7 @@ public function testImitate2Requests(): void $key = '{{ip}}'; $value = '0.0.0.10'; - $adapter = new TimeLimit($key, 1, 1, $this->db); + $adapter = $this->getAdapter($key, 1, 1); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); $this->assertEquals($this->abuseIp->check(), false); @@ -69,7 +34,7 @@ public function testImitate2Requests(): void sleep(1); - $adapter = new TimeLimit($key, 1, 1, $this->db); + $adapter = $this->getAdapter($key, 1, 1); $adapter->setParam($key, $value); $this->abuseIp = new Abuse($adapter); @@ -94,7 +59,8 @@ public function testCleanup(): void sleep(5); // Delete the log - $status = $this->abuse->cleanup(DateTime::addSeconds(new \DateTime(), -1)); + + $status = $this->abuse->cleanup($this->getCleanupDateTime()); $this->assertEquals($status, true); // Check that there are no logs in the DB diff --git a/tests/Abuse/Bench/Base.php b/tests/Abuse/Bench/Base.php new file mode 100644 index 0000000..e78d7af --- /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 0000000..fcba951 --- /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 0000000..b3ad310 --- /dev/null +++ b/tests/Abuse/Bench/RedisBench.php @@ -0,0 +1,23 @@ +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/DatabaseTest.php b/tests/Abuse/DatabaseTest.php new file mode 100755 index 0000000..5927d0f --- /dev/null +++ b/tests/Abuse/DatabaseTest.php @@ -0,0 +1,59 @@ +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(); + } + + $adapter->setParam('{{ip}}', '127.0.0.1'); + $this->abuse = new Abuse($adapter); + } + + public function getAdapter(string $key, int $limit, int $seconds): Adapter + { + return new AdaptersDatabase($key, $limit, $seconds, $this->db); + } + + public function getCleanupDateTime(): string + { + return DateTime::addSeconds(new \DateTime(), -1); + } +} diff --git a/tests/Abuse/RedisTest.php b/tests/Abuse/RedisTest.php new file mode 100644 index 0000000..b29b568 --- /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()); + } +}